From 618224adf620bd72645b3fada4f51e805196f5ca Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 23 Dec 2024 14:39:07 +0900 Subject: [PATCH 01/22] [Feature #20925] Support leading logical operators --- parse.y | 28 ++++++++++++++++++++++ test/ruby/test_syntax.rb | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/parse.y b/parse.y index d2419b0bac2aaa..9e81baa68bfd0b 100644 --- a/parse.y +++ b/parse.y @@ -6985,6 +6985,16 @@ is_identchar(struct parser_params *p, const char *ptr, const char *MAYBE_UNUSED( return rb_enc_isalnum((unsigned char)*ptr, enc) || *ptr == '_' || !ISASCII(*ptr); } +static inline bool +peek_word_at(struct parser_params *p, const char *str, size_t len, int at) +{ + const char *ptr = p->lex.pcur + at; + if (lex_eol_ptr_n_p(p, ptr, len-1)) return false; + if (memcmp(ptr, str, len)) return false; + if (lex_eol_ptr_n_p(p, ptr, len)) return true; + return !is_identchar(p, ptr+len, p->lex.pend, p->enc); +} + static inline int parser_is_identchar(struct parser_params *p) { @@ -10556,7 +10566,24 @@ parser_yylex(struct parser_params *p) token_flush(p); } goto retry; + case 'a': + if (peek_word_at(p, "nd", 2, 0)) goto leading_logical; + goto bol; + case 'o': + if (peek_word_at(p, "r", 1, 0)) goto leading_logical; + goto bol; + case '|': + if (peek(p, '|')) goto leading_logical; + goto bol; case '&': + if (peek(p, '&')) { + leading_logical: + pushback(p, c); + dispatch_delayed_token(p, tIGNORED_NL); + cmd_state = FALSE; + goto retry; + } + /* fall through */ case '.': { dispatch_delayed_token(p, tIGNORED_NL); if (peek(p, '.') == (c == '&')) { @@ -10565,6 +10592,7 @@ parser_yylex(struct parser_params *p) goto retry; } } + bol: default: p->ruby_sourceline--; p->lex.nextline = p->lex.lastline; diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 7e2185be39d47c..19cce29075d49b 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1259,6 +1259,56 @@ def test_fluent_dot assert_valid_syntax("a #\n#\n&.foo\n") end + def test_fluent_and + omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION + + assert_valid_syntax("a\n" "&& foo") + assert_valid_syntax("a\n" "and foo") + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = true + if a + && (a = :ok; true) + a + end + end; + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = true + if a + and (a = :ok; true) + a + end + end; + end + + def test_fluent_or + omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION + + assert_valid_syntax("a\n" "|| foo") + assert_valid_syntax("a\n" "or foo") + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = false + if a + || (a = :ok; true) + a + end + end; + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = false + if a + or (a = :ok; true) + a + end + end; + end + def test_safe_call_in_massign_lhs assert_syntax_error("*a&.x=0", /multiple assignment destination/) assert_syntax_error("a&.x,=0", /multiple assignment destination/) From ff93185f4a2fd6048023a1c8a676b25fe474ef11 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 14:50:57 +0900 Subject: [PATCH 02/22] Remove excludes --- test/.excludes-parsey/Prism/FixturesTest.rb | 1 - test/.excludes-parsey/Prism/LocalsTest.rb | 1 - 2 files changed, 2 deletions(-) delete mode 100644 test/.excludes-parsey/Prism/FixturesTest.rb delete mode 100644 test/.excludes-parsey/Prism/LocalsTest.rb diff --git a/test/.excludes-parsey/Prism/FixturesTest.rb b/test/.excludes-parsey/Prism/FixturesTest.rb deleted file mode 100644 index 452ff4f5058746..00000000000000 --- a/test/.excludes-parsey/Prism/FixturesTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:"test_leading_logical.txt", "Requires Feature #20925 to be implemented on parse.y") diff --git a/test/.excludes-parsey/Prism/LocalsTest.rb b/test/.excludes-parsey/Prism/LocalsTest.rb deleted file mode 100644 index 452ff4f5058746..00000000000000 --- a/test/.excludes-parsey/Prism/LocalsTest.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:"test_leading_logical.txt", "Requires Feature #20925 to be implemented on parse.y") From 91e56471151ea8250fdd9d3d1fd0ccf9e5416ae3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 19 Aug 2025 23:54:51 +0900 Subject: [PATCH 03/22] Get rid of `strcpy` On OpenBSD: ``` ld: warning: namespace.c:731(namespace.o:(rb_namespace_local_extension)): warning: strcpy() is almost always misused, please use strlcpy() ``` --- addr2line.c | 5 +++-- ext/socket/getaddrinfo.c | 4 +--- ext/socket/getnameinfo.c | 15 +++++---------- ext/socket/raddrinfo.c | 4 ++-- namespace.c | 27 +++++++++++++++------------ 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/addr2line.c b/addr2line.c index 745364cc0f78e8..19a6a425c1e138 100644 --- a/addr2line.c +++ b/addr2line.c @@ -637,12 +637,13 @@ follow_debuglink_build_id(const char *build_id, size_t build_id_size, int num_tr obj_info_t **objp, line_info_t *lines, int offset, FILE *errout) { static const char global_debug_dir[] = "/usr/lib/debug/.build-id/"; + static const char debug_suffix[] = ".debug"; const size_t global_debug_dir_len = sizeof(global_debug_dir) - 1; char *p; obj_info_t *o1 = *objp, *o2; size_t i; - if (PATH_MAX < global_debug_dir_len + 1 + build_id_size * 2 + 6) return; + if (PATH_MAX < global_debug_dir_len + build_id_size * 2 + sizeof(debug_suffix)) return; memcpy(binary_filename, global_debug_dir, global_debug_dir_len); p = binary_filename + global_debug_dir_len; @@ -653,7 +654,7 @@ follow_debuglink_build_id(const char *build_id, size_t build_id_size, int num_tr *p++ = tbl[n % 16]; if (i == 0) *p++ = '/'; } - strcpy(p, ".debug"); + memcpy(p, debug_suffix, sizeof(debug_suffix)); append_obj(objp); o2 = *objp; diff --git a/ext/socket/getaddrinfo.c b/ext/socket/getaddrinfo.c index bf0d90129f7601..5b824996552e5f 100644 --- a/ext/socket/getaddrinfo.c +++ b/ext/socket/getaddrinfo.c @@ -171,9 +171,7 @@ static const char *const ai_errlist[] = { #define GET_CANONNAME(ai, str) \ if (pai->ai_flags & AI_CANONNAME) {\ - if (((ai)->ai_canonname = (char *)malloc(strlen(str) + 1)) != NULL) {\ - strcpy((ai)->ai_canonname, (str));\ - } else {\ + if (((ai)->ai_canonname = strdup(str)) == NULL) {\ error = EAI_MEMORY;\ goto free;\ }\ diff --git a/ext/socket/getnameinfo.c b/ext/socket/getnameinfo.c index ae5284fab6fc59..ffda7f98c73f6d 100644 --- a/ext/socket/getnameinfo.c +++ b/ext/socket/getnameinfo.c @@ -158,16 +158,14 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho /* what we should do? */ } else if (flags & NI_NUMERICSERV) { snprintf(numserv, sizeof(numserv), "%d", ntohs(port)); - if (strlen(numserv) + 1 > servlen) + if (strlcpy(serv, numserv, servlen) >= servlen) return ENI_MEMORY; - strcpy(serv, numserv); } else { #if defined(HAVE_GETSERVBYPORT) struct servent *sp = getservbyport(port, (flags & NI_DGRAM) ? "udp" : "tcp"); if (sp) { - if (strlen(sp->s_name) + 1 > servlen) + if (strlcpy(serv, sp->s_name, servlen) >= servlen) return ENI_MEMORY; - strcpy(serv, sp->s_name); } else return ENI_NOSERVNAME; #else @@ -202,9 +200,8 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho if (inet_ntop(afd->a_af, addr, numaddr, sizeof(numaddr)) == NULL) return ENI_SYSTEM; - if (strlen(numaddr) > hostlen) + if (strlcpy(host, numaddr, hostlen) >= hostlen) return ENI_MEMORY; - strcpy(host, numaddr); } else { #ifdef INET6 hp = getipnodebyaddr(addr, afd->a_addrlen, afd->a_af, &h_error); @@ -218,13 +215,12 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho p = strchr(hp->h_name, '.'); if (p) *p = '\0'; } - if (strlen(hp->h_name) + 1 > hostlen) { + if (strlcpy(host, hp->h_name, hostlen) >= hostlen) { #ifdef INET6 freehostent(hp); #endif return ENI_MEMORY; } - strcpy(host, hp->h_name); #ifdef INET6 freehostent(hp); #endif @@ -234,9 +230,8 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho if (inet_ntop(afd->a_af, addr, numaddr, sizeof(numaddr)) == NULL) return ENI_NOHOSTNAME; - if (strlen(numaddr) > hostlen) + if (strlcpy(host, numaddr, hostlen) >= hostlen) return ENI_MEMORY; - strcpy(host, numaddr); } } return SUCCESS; diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 6bacc1c22162a2..22ab34a073a0f1 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -387,7 +387,7 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr if (hostp) { arg->node = buf + hostp_offset; - strcpy(arg->node, hostp); + memcpy(arg->node, hostp, portp_offset - hostp_offset); } else { arg->node = NULL; @@ -395,7 +395,7 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr if (portp) { arg->service = buf + portp_offset; - strcpy(arg->service, portp); + memcpy(arg->service, portp, bufsize - portp_offset); } else { arg->service = NULL; diff --git a/namespace.c b/namespace.c index 55b7580c72e3ab..0e1c5550048527 100644 --- a/namespace.c +++ b/namespace.c @@ -725,28 +725,31 @@ copy_ext_file(char *src_path, char *dst_path) #define IS_DLEXT(e) (strcmp((e), DLEXT) == 0) static void -fname_without_suffix(char *fname, char *rvalue) +fname_without_suffix(const char *fname, char *rvalue, size_t rsize) { - char *pos; - strcpy(rvalue, fname); - for (pos = rvalue + strlen(fname); pos > rvalue; pos--) { + size_t len = strlen(fname); + const char *pos; + for (pos = fname + len; pos > fname; pos--) { if (IS_SOEXT(pos) || IS_DLEXT(pos)) { - *pos = '\0'; - return; + len = pos - fname; + break; } } + if (len > rsize - 1) len = rsize - 1; + memcpy(rvalue, fname, len); + rvalue[len] = '\0'; } static void -escaped_basename(char *path, char *fname, char *rvalue) +escaped_basename(const char *path, const char *fname, char *rvalue, size_t rsize) { - char *pos, *leaf, *found; - leaf = path; + char *pos; + const char *leaf = path, *found; // `leaf + 1` looks uncomfortable (when leaf == path), but fname must not be the top-dir itself while ((found = strstr(leaf + 1, fname)) != NULL) { leaf = found; // find the last occurrence for the path like /etc/my-crazy-lib-dir/etc.so } - strcpy(rvalue, leaf); + strlcpy(rvalue, leaf, rsize); for (pos = rvalue; *pos; pos++) { if (isdirsep(*pos)) { *pos = '+'; @@ -762,8 +765,8 @@ rb_namespace_local_extension(VALUE namespace, VALUE fname, VALUE path) char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname); rb_namespace_t *ns = rb_get_namespace_t(namespace); - fname_without_suffix(fname_ptr, fname2); - escaped_basename(src_path, fname2, basename); + fname_without_suffix(fname_ptr, fname2, sizeof(fname2)); + escaped_basename(src_path, fname2, basename, sizeof(basename)); wrote = sprint_ext_filename(ext_path, sizeof(ext_path), ns->ns_id, NAMESPACE_TMP_PREFIX, basename); if (wrote >= (int)sizeof(ext_path)) { From 9b40d2b27514ac510ff9f8dc84e92e1796e5ffcc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 23:12:38 +0900 Subject: [PATCH 04/22] Stop at max dlext size --- namespace.c | 1 + 1 file changed, 1 insertion(+) diff --git a/namespace.c b/namespace.c index 0e1c5550048527..28e2c63a81940e 100644 --- a/namespace.c +++ b/namespace.c @@ -734,6 +734,7 @@ fname_without_suffix(const char *fname, char *rvalue, size_t rsize) len = pos - fname; break; } + if (fname + len - pos > DLEXT_MAXLEN) break; } if (len > rsize - 1) len = rsize - 1; memcpy(rvalue, fname, len); From bb5cd8e0496f567d9e5c4435e9d24113d30212ba Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 16:36:48 +0900 Subject: [PATCH 05/22] Get rid of `strcpy` and magic numbers --- gc/default/default.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index d5b18d20b96c8b..7264fb799627eb 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -4392,15 +4392,16 @@ static inline void gc_mark_check_t_none(rb_objspace_t *objspace, VALUE obj) { if (RB_UNLIKELY(RB_TYPE_P(obj, T_NONE))) { - char obj_info_buf[256]; - rb_raw_obj_info(obj_info_buf, 256, obj); + enum {info_size = 256}; + char obj_info_buf[info_size]; + rb_raw_obj_info(obj_info_buf, info_size, obj); - char parent_obj_info_buf[256]; + char parent_obj_info_buf[info_size]; if (objspace->rgengc.parent_object == Qfalse) { - strcpy(parent_obj_info_buf, "(none)"); + strlcpy(parent_obj_info_buf, "(none)", info_size); } else { - rb_raw_obj_info(parent_obj_info_buf, 256, objspace->rgengc.parent_object); + rb_raw_obj_info(parent_obj_info_buf, info_size, objspace->rgengc.parent_object); } rb_bug("try to mark T_NONE object (obj: %s, parent: %s)", obj_info_buf, parent_obj_info_buf); From 717ad9f41edc9a2349b6f665dbe49c02e6841370 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 23:16:59 +0900 Subject: [PATCH 06/22] Remove an unused expression --- ext/socket/ipsocket.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index edd270c3adafcd..fa41d89936fa2f 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -208,8 +208,8 @@ 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, VALUE open_timeout, - VALUE _fast_fallback, VALUE _test_mode_settings -) { + VALUE _fast_fallback, VALUE _test_mode_settings) +{ if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); } @@ -423,8 +423,8 @@ select_expires_at( struct timeval *connection_attempt_delay, struct timeval *user_specified_resolv_timeout_at, struct timeval *user_specified_connect_timeout_at, - struct timeval *user_specified_open_timeout_at -) { + struct timeval *user_specified_open_timeout_at) +{ if (any_addrinfos(resolution_store)) { struct timeval *delay; delay = resolution_delay ? resolution_delay : connection_attempt_delay; @@ -526,7 +526,8 @@ in_progress_fds(int fds_size) } static void -remove_connection_attempt_fd(int *fds, int *fds_size, int removing_fd) { +remove_connection_attempt_fd(int *fds, int *fds_size, int removing_fd) +{ int i, j; for (i = 0; i < *fds_size; i++) { @@ -1283,8 +1284,8 @@ 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, VALUE open_timeout, - VALUE fast_fallback, VALUE test_mode_settings -) { + VALUE fast_fallback, VALUE test_mode_settings) +{ if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); } @@ -1315,7 +1316,7 @@ rsock_init_inetsock( ); struct addrinfo *tmp_p = local_res->ai; - for (tmp_p; tmp_p != NULL; tmp_p = tmp_p->ai_next) { + for (; tmp_p != NULL; tmp_p = tmp_p->ai_next) { if (target_families[0] == 0 && tmp_p->ai_family == AF_INET6) { target_families[0] = AF_INET6; resolving_family_size++; From 234f4c0bb62b394971eb1044c9c0b1f994d1d4c4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 12 Sep 2025 23:17:23 +0900 Subject: [PATCH 07/22] Fix dangling pointers --- prism/util/pm_string.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prism/util/pm_string.c b/prism/util/pm_string.c index 75422fbdf2c41d..cf79885fddbdff 100644 --- a/prism/util/pm_string.c +++ b/prism/util/pm_string.c @@ -182,7 +182,7 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { if (size == 0) { close(fd); - const uint8_t source[] = ""; + static const uint8_t source[] = ""; *string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 }; return PM_STRING_INIT_SUCCESS; } @@ -278,7 +278,7 @@ pm_string_file_init(pm_string_t *string, const char *filepath) { size_t size = (size_t) sb.st_size; if (size == 0) { close(fd); - const uint8_t source[] = ""; + static const uint8_t source[] = ""; *string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 }; return PM_STRING_INIT_SUCCESS; } From 9620964f9c1f2253b350339ee96decf0c04dbbbf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 18:02:55 +0900 Subject: [PATCH 08/22] Use API version for syntax version instead of program version --- depend | 2 -- prism_compile.c | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/depend b/depend index dbc002d5861080..b9d91faa2a05a2 100644 --- a/depend +++ b/depend @@ -1415,7 +1415,6 @@ compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h compile.$(OBJEXT): $(top_srcdir)/prism/version.h compile.$(OBJEXT): $(top_srcdir)/prism_compile.c -compile.$(OBJEXT): $(top_srcdir)/version.h compile.$(OBJEXT): {$(VPATH)}assert.h compile.$(OBJEXT): {$(VPATH)}atomic.h compile.$(OBJEXT): {$(VPATH)}backward/2/assume.h @@ -1606,7 +1605,6 @@ compile.$(OBJEXT): {$(VPATH)}prism_compile.h compile.$(OBJEXT): {$(VPATH)}ractor.h compile.$(OBJEXT): {$(VPATH)}re.h compile.$(OBJEXT): {$(VPATH)}regex.h -compile.$(OBJEXT): {$(VPATH)}revision.h compile.$(OBJEXT): {$(VPATH)}ruby_assert.h compile.$(OBJEXT): {$(VPATH)}ruby_atomic.h compile.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/prism_compile.c b/prism_compile.c index 70081f3d95ad1f..74f10024cb29ac 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -1,5 +1,5 @@ #include "prism.h" -#include "version.h" +#include "ruby/version.h" /** * This compiler defines its own concept of the location of a node. We do this @@ -11501,7 +11501,7 @@ pm_parse_stdin(pm_parse_result_t *result) #define PM_VERSION_FOR_RELEASE_IMPL(major, minor) PM_OPTIONS_VERSION_CRUBY_##major##_##minor void pm_options_version_for_current_ruby_set(pm_options_t *options) { - options->version = PM_VERSION_FOR_RELEASE(RUBY_VERSION_MAJOR, RUBY_VERSION_MINOR); + options->version = PM_VERSION_FOR_RELEASE(RUBY_API_VERSION_MAJOR, RUBY_API_VERSION_MINOR); } #undef NEW_ISEQ From caa5d8cdd7483647013af5e3d1701b0ed3ec8785 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 19:29:05 +0900 Subject: [PATCH 09/22] Exclude `JSON::GenericObject` test It depends on ostruct gem that is no longer a part of the default gems, and the all tests are just skipped with a warning. --- test/.excludes/JSONGenericObjectTest.rb | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 test/.excludes/JSONGenericObjectTest.rb diff --git a/test/.excludes/JSONGenericObjectTest.rb b/test/.excludes/JSONGenericObjectTest.rb new file mode 100644 index 00000000000000..820a6a01200182 --- /dev/null +++ b/test/.excludes/JSONGenericObjectTest.rb @@ -0,0 +1,4 @@ +# ostruct will be loaded when JSON::GenericObject is autoloaded. By +# removing all test methods, the autoload in `setup` is not triggered. + +exclude /test_/, 'JSON::GenericObject needs ostruct gem' From 46095d26805de11571697b30adfe234db18efcc9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 15:03:56 +0900 Subject: [PATCH 10/22] auto-style.rb: Allow to adjust the given files --- tool/auto-style.rb | 144 +++++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 65 deletions(-) diff --git a/tool/auto-style.rb b/tool/auto-style.rb index b673e3d1771042..921ffe11f922f6 100755 --- a/tool/auto-style.rb +++ b/tool/auto-style.rb @@ -11,9 +11,11 @@ class Git def initialize(oldrev, newrev, branch = nil) @oldrev = oldrev - @newrev = newrev.empty? ? 'HEAD' : newrev + @newrev = !newrev || newrev.empty? ? 'HEAD' : newrev @branch = branch + return unless oldrev + # GitHub may not fetch github.event.pull_request.base.sha at checkout git('log', '--format=%H', '-1', @oldrev, out: IO::NULL, err: [:child, :out]) or git('fetch', '--depth=1', 'origin', @oldrev) @@ -59,7 +61,7 @@ def commit(log, *files) end def push - git('push', 'origin', @branch) + git('push', 'origin', @branch) if @branch end def diff @@ -181,85 +183,97 @@ def with_clean_env addr2line.c io_buffer.c prism*.c scheduler.c ] -oldrev, newrev, pushref = ARGV -unless dry_run = pushref.empty? - branch = IO.popen(['git', 'rev-parse', '--symbolic', '--abbrev-ref', pushref], &:read).strip -end -git = Git.new(oldrev, newrev, branch) +def adjust_styles(files) + trailing = eofnewline = expandtab = indent = false -updated_files = git.updated_paths -files = updated_files.select {|l| - /^\d/ !~ l and /\.bat\z/ !~ l and - (/\A(?:config|[Mm]akefile|GNUmakefile|README)/ =~ File.basename(l) or - /\A\z|\.(?:[chsy]|\d+|e?rb|tmpl|bas[eh]|z?sh|in|ma?k|def|src|trans|rdoc|ja|en|el|sed|awk|p[ly]|scm|mspec|html|rs)\z/ =~ File.extname(l)) -} -files.select! {|n| File.file?(n) } -files.reject! do |f| - IGNORED_FILES.any? { |re| f.match(re) } -end -if files.empty? - puts "No files are an auto-style target:\n#{updated_files.join("\n")}" - exit -end + edited_files = files.select do |f| + src = File.binread(f) rescue next + eofnewline = eofnewline0 = true if src.sub!(/(? Date: Sat, 13 Sep 2025 15:04:20 +0900 Subject: [PATCH 11/22] auto-style.rb: Adjust .y file as same as .c --- tool/auto-style.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/auto-style.rb b/tool/auto-style.rb index 921ffe11f922f6..259ed377bc7036 100755 --- a/tool/auto-style.rb +++ b/tool/auto-style.rb @@ -217,7 +217,7 @@ def adjust_styles(files) end end - if File.fnmatch?("*.[ch]", f, File::FNM_PATHNAME) && + if File.fnmatch?("*.[chy]", f, File::FNM_PATHNAME) && !DIFFERENT_STYLE_FILES.any? {|pat| File.fnmatch?(pat, f, File::FNM_PATHNAME)} indent0 = true if src.gsub!(/^\w+\([^\n]*?\)\K[ \t]*(?=\{( *\\)?$)/, '\1' "\n") indent0 = true if src.gsub!(/^([ \t]*)\}\K[ \t]*(?=else\b.*?( *\\)?$)/, '\2' "\n" '\1') From d2cea4b68848bf6d8edb79b2da688b1d85e952a5 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 20:10:42 +0900 Subject: [PATCH 12/22] * adjust indents. [ci skip] --- parse.y | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/parse.y b/parse.y index 9e81baa68bfd0b..6fac3b8ec79760 100644 --- a/parse.y +++ b/parse.y @@ -5897,7 +5897,8 @@ strings : string { if (!$1) { $$ = NEW_STR(STRING_NEW0(), &@$); - } else { + } + else { $$ = evstr2dstr(p, $1); } /*% ripper: $:1 %*/ @@ -6922,7 +6923,8 @@ parser_dispatch_delayed_token(struct parser_params *p, enum yytokentype t, int l if (p->keep_tokens) { /* p->delayed.token is freed by rb_parser_tokens_free */ parser_append_tokens(p, p->delayed.token, t, line); - } else { + } + else { rb_parser_string_free(p, p->delayed.token); } From ea8b346cbc03bae4857d32456bbd2895ae82e770 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 30 Aug 2025 13:03:15 +0900 Subject: [PATCH 13/22] Suppress warning from net/http.rb in ruby 3.1 When `-F` option is given to strip comments in bundled_gems file, `$;` is set to the regexp. On the other hand, old net/http.rb contained old CVS keyword expansion that is `split` with the default separator, and non-nil `$;` causes a warning. ```ruby Revision = %q$Revision$.split[1] ``` --- tool/downloader.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tool/downloader.rb b/tool/downloader.rb index 14f18747f3849e..39ebf44a83cbb0 100644 --- a/tool/downloader.rb +++ b/tool/downloader.rb @@ -6,7 +6,9 @@ require 'fileutils' require 'open-uri' require 'pathname' +verbose, $VERBOSE = $VERBOSE, nil require 'net/https' +$VERBOSE = verbose class Downloader def self.find(dlname) From aaa9c1913655acd80aea7e7fd2cf2a3fc1db2083 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 22:19:47 +0900 Subject: [PATCH 14/22] [DOC] Markup the autolink excluded word library name `Set` --- NEWS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4679ef9a289380..1d96fc3ad2e1e0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -98,9 +98,9 @@ Note: We're only listing outstanding class updates. * `Ractor#close_incoming` and `Ractor#close_outgoing` were removed. -* Set +* `Set` - * Set is now a core class, instead of an autoloaded stdlib class. + * `Set` is now a core class, instead of an autoloaded stdlib class. [[Feature #21216]] * String From c68a1c3085466436bc790e1039c98897a762c5ac Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 22:24:30 +0900 Subject: [PATCH 15/22] [DOC] [Feature #20925] Add leading logical to NEWS --- NEWS.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/NEWS.md b/NEWS.md index 1d96fc3ad2e1e0..790ea9e72630ca 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,25 @@ Note that each entry is kept to a minimum, see links for details. * `*nil` no longer calls `nil.to_a`, similar to how `**nil` does not call `nil.to_hash`. [[Feature #21047]] +* Logical binary operators (`||`, `&&`, `and` and `or`) at the + beginning of a line continue the previous line, like fluent dot. + The following two code are equal: + + ```ruby + if condition1 + && condition2 + ... + end + ``` + + ```ruby + if condition1 && condition2 + ... + end + ``` + + [[Feature #20925]] + ## Core classes updates Note: We're only listing outstanding class updates. @@ -285,6 +304,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 [Feature #20610]: https://bugs.ruby-lang.org/issues/20610 [Feature #20724]: https://bugs.ruby-lang.org/issues/20724 +[Feature #20925]: https://bugs.ruby-lang.org/issues/20925 [Feature #21047]: https://bugs.ruby-lang.org/issues/21047 [Bug #21049]: https://bugs.ruby-lang.org/issues/21049 [Feature #21166]: https://bugs.ruby-lang.org/issues/21166 From c5feae96217d4b4d5d270abe6a71f1b560232a9a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 13 Sep 2025 22:38:38 +0900 Subject: [PATCH 16/22] Remove stale line [ci skip] --- .github/workflows/macos.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 0b9d5d049d635e..70b2bc9d68d109 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -46,7 +46,6 @@ jobs: - test_task: check os: macos-15 extra_checks: [capi] - capi_check: capi - test_task: check os: macos-13 fail-fast: false From 4f4b4e3b37c884093a165801b5028c14a7de1604 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Sep 2025 20:51:40 -0400 Subject: [PATCH 17/22] Fill in lead num for blocks with `it` Fixes [Bug #21256] Co-Authored-By: Earlopain <14981592+Earlopain@users.noreply.github.com> --- prism_compile.c | 6 ++++++ test/ruby/test_syntax.rb | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/prism_compile.c b/prism_compile.c index 74f10024cb29ac..9a6197f7cb80f1 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -6740,6 +6740,12 @@ pm_compile_scope_node(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_nod body->param.flags.has_lead = true; } + // Fill in the anonymous `it` parameter, if it exists + if (scope_node->parameters && PM_NODE_TYPE_P(scope_node->parameters, PM_IT_PARAMETERS_NODE)) { + body->param.lead_num = 1; + body->param.flags.has_lead = true; + } + //********END OF STEP 3********** //********STEP 4********** diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 19cce29075d49b..94a2e03940bf5b 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -2004,6 +2004,19 @@ def test_it assert_equal(/9/, eval('9.then { /#{it}/o }')) end + def test_it_with_splat_super_method + bug21256 = '[ruby-core:121592] [Bug #21256]' + + a = Class.new do + define_method(:foo) { it } + end + b = Class.new(a) do + def foo(*args) = super + end + + assert_equal(1, b.new.foo(1), bug21256) + end + def test_value_expr_in_condition mesg = /void value expression/ assert_syntax_error("tap {a = (true ? next : break)}", mesg) From d781d69a06e7d4eef3334e44a25b02d05bad1e2d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 12 Sep 2025 21:13:40 -0400 Subject: [PATCH 18/22] Fix prism error messages with multibyte truncation When a line is going to be displayed in an error message that contains multibyte characters, we need to respect the encoding of the source and truncate only at a character boundary, as opposed to a raw byte boundary. Fixes [Bug #21528] --- prism_compile.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/prism_compile.c b/prism_compile.c index 9a6197f7cb80f1..578e6f240f98dc 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -10627,7 +10627,26 @@ pm_parse_errors_format_line(const pm_parser_t *parser, const pm_newline_list_t * // Here we determine if we should truncate the end of the line. bool truncate_end = false; if ((column_end != 0) && ((end - (start + column_end)) >= PM_ERROR_TRUNCATE)) { - end = start + column_end + PM_ERROR_TRUNCATE; + const uint8_t *end_candidate = start + column_end + PM_ERROR_TRUNCATE; + + for (const uint8_t *ptr = start; ptr < end_candidate;) { + size_t char_width = parser->encoding->char_width(ptr, parser->end - ptr); + + // If we failed to decode a character, then just bail out and + // truncate at the fixed width. + if (char_width == 0) break; + + // If this next character would go past the end candidate, + // then we need to truncate before it. + if (ptr + char_width > end_candidate) { + end_candidate = ptr; + break; + } + + ptr += char_width; + } + + end = end_candidate; truncate_end = true; } From e74524616013c616744ebf8168f1ad57eee74a05 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 12 Sep 2025 14:30:48 -0700 Subject: [PATCH 19/22] [ruby/prism] Revert "Merge pull request #3606 from tenderlove/clear-flags" This reverts commit https://github.com/ruby/prism/commit/4052d93cf852, reversing changes made to https://github.com/ruby/prism/commit/47143d17b3f7. https://github.com/ruby/prism/commit/f117ec6354 --- prism/prism.c | 4 ---- test/prism/result/static_literals_test.rb | 5 ----- 2 files changed, 9 deletions(-) diff --git a/prism/prism.c b/prism/prism.c index 337b77637b748f..06419d13789e45 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -5279,10 +5279,6 @@ pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_ switch (PM_NODE_TYPE(part)) { case PM_STRING_NODE: - // If inner string is not frozen, clear flags for this string - if (!PM_NODE_FLAG_P(part, PM_STRING_FLAGS_FROZEN)) { - CLEAR_FLAGS(node); - } part->flags = (pm_node_flags_t) ((part->flags | PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN) & ~PM_STRING_FLAGS_MUTABLE); break; case PM_INTERPOLATED_STRING_NODE: diff --git a/test/prism/result/static_literals_test.rb b/test/prism/result/static_literals_test.rb index cc070279169aba..dcfc692897cd66 100644 --- a/test/prism/result/static_literals_test.rb +++ b/test/prism/result/static_literals_test.rb @@ -4,11 +4,6 @@ module Prism class StaticLiteralsTest < TestCase - def test_concatenanted_string_literal_is_not_static - node = Prism.parse_statement("'a' 'b'") - refute_predicate node, :static_literal? - end - def test_static_literals assert_warning("1") assert_warning("0xA", "10", "10") From f4ce5e90b2b9a4ccc7b4a0a25416c577142d2877 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 13 Sep 2025 09:53:03 -0400 Subject: [PATCH 20/22] [ruby/prism] Bump to v1.5.1 https://github.com/ruby/prism/commit/cac5118884 --- lib/prism/prism.gemspec | 2 +- prism/extension.h | 2 +- prism/templates/lib/prism/serialize.rb.erb | 2 +- prism/version.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 74b9971a00a3e8..65305d0ec1a244 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "1.5.0" + spec.version = "1.5.1" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/prism/extension.h b/prism/extension.h index f4cb9c438d5ed8..b18e770d9213f7 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "1.5.0" +#define EXPECTED_PRISM_VERSION "1.5.1" #include #include diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index a5909e15bb67fe..366878f709250d 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -14,7 +14,7 @@ module Prism # The patch version of prism that we are expecting to find in the serialized # strings. - PATCH_VERSION = 0 + PATCH_VERSION = 1 # Deserialize the dumped output from a request to parse or parse_file. # diff --git a/prism/version.h b/prism/version.h index 697c7b5ad6f37e..697ba0647df1ec 100644 --- a/prism/version.h +++ b/prism/version.h @@ -19,11 +19,11 @@ /** * The patch version of the Prism library as an int. */ -#define PRISM_VERSION_PATCH 0 +#define PRISM_VERSION_PATCH 1 /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "1.5.0" +#define PRISM_VERSION "1.5.1" #endif From 6c9408d1b2c48087bb639bc2ad59317f0297a77e Mon Sep 17 00:00:00 2001 From: git Date: Sat, 13 Sep 2025 14:01:31 +0000 Subject: [PATCH 21/22] Update default gems list at f4ce5e90b2b9a4ccc7b4a0a25416c5 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 790ea9e72630ca..7157700816c3e8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -186,7 +186,7 @@ The following default gems are updated. * io-wait 0.3.2 * json 2.13.2 * optparse 0.7.0.dev.2 -* prism 1.5.0 +* prism 1.5.1 * psych 5.2.6 * resolv 0.6.2 * stringio 3.1.8.dev From b897a47ae965367eb154eec8abe618711a91f1c1 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 13 Sep 2025 10:09:09 -0400 Subject: [PATCH 22/22] [ruby/prism] Documentation for Prism::Translation::Parser Make it clear that it parses with the most recent version of Ruby syntax. https://github.com/ruby/prism/commit/7285d1fbab --- lib/prism/translation/parser.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index a7888f77ecced1..1ad7a193c4f18b 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -19,6 +19,13 @@ module Translation # whitequark/parser gem's syntax tree. It inherits from the base parser for # the parser gem, and overrides the parse* methods to parse with prism and # then translate. + # + # Note that this version of the parser always parses using the latest + # version of Ruby syntax supported by Prism. If you want specific version + # support, use one of the version-specific subclasses, such as + # `Prism::Translation::Parser34`. If you want to parse using the same + # version of Ruby syntax as the currently running version of Ruby, use + # `Prism::Translation::ParserCurrent`. class Parser < ::Parser::Base Diagnostic = ::Parser::Diagnostic # :nodoc: private_constant :Diagnostic @@ -77,7 +84,7 @@ def initialize(builder = Prism::Translation::Parser::Builder.new, parser: Prism) end def version # :nodoc: - 34 + 35 end # The default encoding for Ruby files is UTF-8.