diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 5163076be5d98a..220f0ae2028b59 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -114,7 +114,6 @@ jobs: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} - - uses: ./.github/actions/setup/directories with: srcdir: src diff --git a/LEGAL b/LEGAL index a05cd6eaa3b01b..cb1b867c23bd81 100644 --- a/LEGAL +++ b/LEGAL @@ -704,32 +704,7 @@ mentioned below. [ext/json/vendor/fpconv.c] - This file is under the Boost Software License. - - >>> - Boost Software License - Version 1.0 - August 17th, 2003 - - Permission is hereby granted, free of charge, to any person or organization - obtaining a copy of the software and accompanying documentation covered by - this license (the "Software") to use, reproduce, display, distribute, - execute, and transmit the Software, and to prepare derivative works of the - Software, and to permit third-parties to whom the Software is furnished to - do so, all subject to the following: - - The copyright notices in the Software and this entire statement, including - the above license grant, this restriction and the following disclaimer, - must be included in all copies of the Software, in whole or in part, and - all derivative works of the Software, unless such copies or derivative - works are solely in the form of machine-executable object code generated by - a source language processor. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT - SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. + This file is under the {Boost Software License}[rdoc-ref:@Boost+Software+License+1.0]. [ext/json/vendor/jeaiii-ltoa.h] @@ -740,6 +715,11 @@ mentioned below. {MIT License}[rdoc-ref:@MIT+License] +[ext/json/ext/vendor/ryu.h] + This file is adapted from the Ryu algorithm by Ulf Adams https://github.com/ulfjack/ryu. + It is dual-licensed under {Apache License 2.0}[rdoc-ref:@Apache+License+2.0] OR + {Boost Software License 1.0}[rdoc-ref:@Boost+Software+License+1.0]. + [ext/psych] [test/psych] @@ -1086,3 +1066,236 @@ mentioned below. From ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change paragraph 3 above is now null and void. + +== Boost Software License 1.0 + +>>> + Boost Software License - Version 1.0 - August 17th, 2003 + + Permission is hereby granted, free of charge, to any person or organization + obtaining a copy of the software and accompanying documentation covered by + this license (the "Software") to use, reproduce, display, distribute, + execute, and transmit the Software, and to prepare derivative works of the + Software, and to permit third-parties to whom the Software is furnished to + do so, all subject to the following: + + The copyright notices in the Software and this entire statement, including + the above license grant, this restriction and the following disclaimer, + must be included in all copies of the Software, in whole or in part, and + all derivative works of the Software, unless such copies or derivative + works are solely in the form of machine-executable object code generated by + a source language processor. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + +== Apache License 2.0 + +>>> + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + a. You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + b. You must cause any modified files to carry prominent notices + stating that You changed the files; and + + c. You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + d. If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + >>> + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 65ef07fb73e291..fef55ffd688003 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -754,6 +754,37 @@ def ractor_local_globals end } +# eval with outer locals in a Ractor raises SyntaxError +# [Bug #21522] +assert_equal 'SyntaxError', %q{ + outer = 42 + r = Ractor.new do + eval("outer") + end + begin + r.value + rescue Ractor::RemoteError => e + e.cause.class + end +} + +# eval of an undefined name in a Ractor raises NameError +assert_equal 'NameError', %q{ + r = Ractor.new do + eval("totally_undefined_name") + end + begin + r.value + rescue Ractor::RemoteError => e + e.cause.class + end +} + +# eval of a local defined inside the Ractor works +assert_equal '99', %q{ + Ractor.new { inner = 99; eval("inner").to_s }.value +} + # ivar in shareable-objects are not allowed to access from non-main Ractor assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", <<~'RUBY', frozen_string_literal: false class C diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index 6e0618890de6a7..88056146036411 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -217,7 +217,7 @@ def visit_begin_node(node) rescue_clause.exceptions.any? ? builder.array(nil, visit_all(rescue_clause.exceptions), nil) : nil, token(rescue_clause.operator_loc), visit(rescue_clause.reference), - srange_find(find_start_offset, find_end_offset, ";"), + srange_semicolon(find_start_offset, find_end_offset), visit(rescue_clause.statements) ) end until (rescue_clause = rescue_clause.subsequent).nil? @@ -323,7 +323,7 @@ def visit_call_node(node) visit_all(arguments), token(node.closing_loc), ), - srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, "="), + token(node.equal_loc), visit(node.arguments.arguments.last) ), block @@ -340,7 +340,7 @@ def visit_call_node(node) if name.end_with?("=") && !message_loc.slice.end_with?("=") && node.arguments && block.nil? builder.assign( builder.attr_asgn(visit(node.receiver), call_operator, token(message_loc)), - srange_find(message_loc.end_offset, node.arguments.location.start_offset, "="), + token(node.equal_loc), visit(node.arguments.arguments.last) ) else @@ -789,7 +789,7 @@ def visit_for_node(node) if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else - srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, ";") + srange_semicolon(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset) end, visit(node.statements), token(node.end_keyword_loc) @@ -921,7 +921,7 @@ def visit_if_node(node) if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset) end, visit(node.statements), case node.subsequent @@ -987,7 +987,7 @@ def visit_in_node(node) if (then_loc = node.then_loc) token(then_loc) else - srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, ";") + srange_semicolon(node.pattern.location.end_offset, node.statements&.location&.start_offset) end, visit(node.statements) ) @@ -1808,7 +1808,7 @@ def visit_unless_node(node) if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset) end, visit(node.else_clause), token(node.else_clause&.else_keyword_loc), @@ -1839,7 +1839,7 @@ def visit_until_node(node) if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset) end, visit(node.statements), token(node.closing_loc) @@ -1863,7 +1863,7 @@ def visit_when_node(node) if (then_keyword_loc = node.then_keyword_loc) token(then_keyword_loc) else - srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, ";") + srange_semicolon(node.conditions.last.location.end_offset, node.statements&.location&.start_offset) end, visit(node.statements) ) @@ -1883,7 +1883,7 @@ def visit_while_node(node) if (do_keyword_loc = node.do_keyword_loc) token(do_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";") + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset) end, visit(node.statements), token(node.closing_loc) @@ -2012,16 +2012,16 @@ def srange_offsets(start_offset, end_offset) Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset]) end - # Constructs a new source range by finding the given character between - # the given start offset and end offset. If the needle is not found, it - # returns nil. Importantly it does not search past newlines or comments. + # Constructs a new source range by finding a semicolon between the given + # start offset and end offset. If the semicolon is not found, it returns + # nil. Importantly it does not search past newlines or comments. # # Note that end_offset is allowed to be nil, in which case this will # search until the end of the string. - def srange_find(start_offset, end_offset, character) - if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*#{character}/]) + def srange_semicolon(start_offset, end_offset) + if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*;/]) final_offset = start_offset + match.bytesize - [character, Range.new(source_buffer, offset_cache[final_offset - character.bytesize], offset_cache[final_offset])] + [";", Range.new(source_buffer, offset_cache[final_offset - 1], offset_cache[final_offset])] end end diff --git a/prism/config.yml b/prism/config.yml index ed5c9d8b9cde98..7d71d52de487df 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -1515,6 +1515,16 @@ nodes: foo(bar) ^ + - name: equal_loc + type: location? + comment: | + Represents the location of the equal sign, in the case that this is an attribute write. + + foo.bar = value + ^ + + foo[bar] = value + ^ - name: block type: node? kind: diff --git a/prism/prism.c b/prism/prism.c index 95e7d0905040b1..6a77dd0febd5d3 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -2650,6 +2650,7 @@ pm_call_node_create(pm_parser_t *parser, pm_node_flags_t flags) { .opening_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .arguments = NULL, .closing_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, + .equal_loc = PM_OPTIONAL_LOCATION_NOT_PROVIDED_VALUE, .block = NULL, .name = 0 }; @@ -2721,6 +2722,8 @@ pm_call_node_binary_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t return node; } +static const uint8_t * parse_operator_symbol_name(const pm_token_t *); + /** * Allocate and initialize a new CallNode node from a call expression. */ @@ -2749,7 +2752,11 @@ pm_call_node_call_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t *o pm_node_flag_set((pm_node_t *)node, PM_CALL_NODE_FLAGS_SAFE_NAVIGATION); } - node->name = pm_parser_constant_id_token(parser, message); + /** + * If the final character is `@` as is the case for `foo.~@`, + * we should ignore the @ in the same way we do for symbols. + */ + node->name = pm_parser_constant_id_location(parser, message->start, parse_operator_symbol_name(message)); return node; } @@ -13804,6 +13811,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_arguments_node_arguments_append(arguments, value); call->base.location.end = arguments->base.location.end; + call->equal_loc = PM_LOCATION_TOKEN_VALUE(operator); parse_write_name(parser, &call->name); pm_node_flag_set((pm_node_t *) call, PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE | pm_implicit_array_write_flags(value, PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY)); @@ -13825,6 +13833,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod // Replace the name with "[]=". call->name = pm_parser_constant_id_constant(parser, "[]=", 3); + call->equal_loc = PM_LOCATION_TOKEN_VALUE(operator); // Ensure that the arguments for []= don't contain keywords pm_index_arguments_check(parser, call->arguments, call->block); @@ -19702,7 +19711,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_parser_scope_pop(parser); /** - * If the final character is @. As is the case when defining + * If the final character is `@` as is the case when defining * methods to override the unary operators, we should ignore * the @ in the same way we do for symbols. */ diff --git a/spec/ruby/core/io/binread_spec.rb b/spec/ruby/core/io/binread_spec.rb index 418e89213b8daf..9e36b84da97350 100644 --- a/spec/ruby/core/io/binread_spec.rb +++ b/spec/ruby/core/io/binread_spec.rb @@ -45,7 +45,7 @@ -> { IO.binread @fname, 0, -1 }.should raise_error(Errno::EINVAL) end - ruby_version_is "3.3" do + ruby_version_is "3.3"..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do cmd = "|echo ok" diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb index c361d27879214d..6abe8901bac7a0 100644 --- a/spec/ruby/core/io/foreach_spec.rb +++ b/spec/ruby/core/io/foreach_spec.rb @@ -14,33 +14,35 @@ IO.foreach(@name) { $..should == @count += 1 } end - describe "when the filename starts with |" do - it "gets data from the standard out of the subprocess" do - cmd = "|sh -c 'echo hello;echo line2'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello&echo line2" - end + ruby_version_is ""..."4.0" do + describe "when the filename starts with |" do + it "gets data from the standard out of the subprocess" do + cmd = "|sh -c 'echo hello;echo line2'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello&echo line2" + end - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach(cmd) { |l| ScratchPad << l } + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.foreach(cmd) { |l| ScratchPad << l } + end + ScratchPad.recorded.should == ["hello\n", "line2\n"] end - ScratchPad.recorded.should == ["hello\n", "line2\n"] - end - platform_is_not :windows do - it "gets data from a fork when passed -" do - parent_pid = $$ + platform_is_not :windows do + it "gets data from a fork when passed -" do + parent_pid = $$ - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach("|-") { |l| ScratchPad << l } - end + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.foreach("|-") { |l| ScratchPad << l } + end - if $$ == parent_pid - ScratchPad.recorded.should == ["hello\n", "from a fork\n"] - else # child - puts "hello" - puts "from a fork" - exit! + if $$ == parent_pid + ScratchPad.recorded.should == ["hello\n", "from a fork\n"] + else # child + puts "hello" + puts "from a fork" + exit! + end end end end diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index 567daa55dfc464..988ec2ce30df25 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -168,76 +168,78 @@ end end -describe "IO.read from a pipe" do - it "runs the rest as a subprocess and returns the standard output" do - cmd = "|sh -c 'echo hello'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello" - end - - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read(cmd).should == "hello\n" - end - end - - platform_is_not :windows do - it "opens a pipe to a fork if the rest is -" do - str = nil - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - str = IO.read("|-") +ruby_version_is ""..."4.0" do + describe "IO.read from a pipe" do + it "runs the rest as a subprocess and returns the standard output" do + cmd = "|sh -c 'echo hello'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello" end - if str # parent - str.should == "hello from child\n" - else #child - puts "hello from child" - exit! + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read(cmd).should == "hello\n" end end - end - it "reads only the specified number of bytes requested" do - cmd = "|sh -c 'echo hello'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello" - end + platform_is_not :windows do + it "opens a pipe to a fork if the rest is -" do + str = nil + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + str = IO.read("|-") + end - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read(cmd, 1).should == "h" + if str # parent + str.should == "hello from child\n" + else #child + puts "hello from child" + exit! + end + end end - end - platform_is_not :windows do - it "raises Errno::ESPIPE if passed an offset" do - -> { - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read("|sh -c 'echo hello'", 1, 1) - end - }.should raise_error(Errno::ESPIPE) + it "reads only the specified number of bytes requested" do + cmd = "|sh -c 'echo hello'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello" + end + + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read(cmd, 1).should == "h" + end end - end - quarantine! do # The process tried to write to a nonexistent pipe. - platform_is :windows do - # TODO: It should raise Errno::ESPIPE on Windows as well - # once https://bugs.ruby-lang.org/issues/12230 is fixed. - it "raises Errno::EINVAL if passed an offset" do + platform_is_not :windows do + it "raises Errno::ESPIPE if passed an offset" do -> { suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read("|cmd.exe /C echo hello", 1, 1) + IO.read("|sh -c 'echo hello'", 1, 1) end - }.should raise_error(Errno::EINVAL) + }.should raise_error(Errno::ESPIPE) end end - end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - IO.read(cmd) - }.should complain(/IO process creation with a leading '\|'/) + quarantine! do # The process tried to write to a nonexistent pipe. + platform_is :windows do + # TODO: It should raise Errno::ESPIPE on Windows as well + # once https://bugs.ruby-lang.org/issues/12230 is fixed. + it "raises Errno::EINVAL if passed an offset" do + -> { + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read("|cmd.exe /C echo hello", 1, 1) + end + }.should raise_error(Errno::EINVAL) + end + end + end + + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation" do + cmd = "|echo ok" + -> { + IO.read(cmd) + }.should complain(/IO process creation with a leading '\|'/) + end end end end diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb index 3a6ff3d0f34230..b4770775d1e813 100644 --- a/spec/ruby/core/io/readlines_spec.rb +++ b/spec/ruby/core/io/readlines_spec.rb @@ -174,45 +174,47 @@ $_.should == "test" end - describe "when passed a string that starts with a |" do - it "gets data from the standard out of the subprocess" do - cmd = "|sh -c 'echo hello;echo line2'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello&echo line2" - end - - lines = nil - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - lines = IO.readlines(cmd) - end - lines.should == ["hello\n", "line2\n"] - end + ruby_version_is ""..."4.0" do + describe "when passed a string that starts with a |" do + it "gets data from the standard out of the subprocess" do + cmd = "|sh -c 'echo hello;echo line2'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello&echo line2" + end - platform_is_not :windows do - it "gets data from a fork when passed -" do lines = nil suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - lines = IO.readlines("|-") + lines = IO.readlines(cmd) end + lines.should == ["hello\n", "line2\n"] + end - if lines # parent - lines.should == ["hello\n", "from a fork\n"] - else - puts "hello" - puts "from a fork" - exit! + platform_is_not :windows do + it "gets data from a fork when passed -" do + lines = nil + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + lines = IO.readlines("|-") + end + + if lines # parent + lines.should == ["hello\n", "from a fork\n"] + else + puts "hello" + puts "from a fork" + exit! + end end end end - end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - IO.readlines(cmd) - }.should complain(/IO process creation with a leading '\|'/) + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + IO.readlines(cmd) + }.should complain(/IO process creation with a leading '\|'/) + end end end diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index 4a26f8dbaf9fec..e58100f8467d9c 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -220,7 +220,7 @@ end end - ruby_version_is "3.3" do + ruby_version_is "3.3"..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do -> { diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb index 6d00af395d9d3c..b967d5044ba92b 100644 --- a/spec/ruby/core/kernel/open_spec.rb +++ b/spec/ruby/core/kernel/open_spec.rb @@ -27,64 +27,66 @@ open(@name, "r") { |f| f.gets }.should == @content end - platform_is_not :windows, :wasi do - it "opens an io when path starts with a pipe" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @io = open("|date") + ruby_version_is ""..."4.0" do + platform_is_not :windows, :wasi do + it "opens an io when path starts with a pipe" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @io = open("|date") + end + begin + @io.should be_kind_of(IO) + @io.read + ensure + @io.close + end end - begin - @io.should be_kind_of(IO) - @io.read - ensure - @io.close - end - end - it "opens an io when called with a block" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @output = open("|date") { |f| f.read } + it "opens an io when called with a block" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @output = open("|date") { |f| f.read } + end + @output.should_not == '' end - @output.should_not == '' - end - it "opens an io for writing" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - -> { - bytes = open("|cat", "w") { |io| io.write(".") } - bytes.should == 1 - }.should output_to_fd(".") + it "opens an io for writing" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + -> { + bytes = open("|cat", "w") { |io| io.write(".") } + bytes.should == 1 + }.should output_to_fd(".") + end end end - end - platform_is :windows do - it "opens an io when path starts with a pipe" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @io = open("|date /t") + platform_is :windows do + it "opens an io when path starts with a pipe" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @io = open("|date /t") + end + begin + @io.should be_kind_of(IO) + @io.read + ensure + @io.close + end end - begin - @io.should be_kind_of(IO) - @io.read - ensure - @io.close - end - end - it "opens an io when called with a block" do - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @output = open("|date /t") { |f| f.read } + it "opens an io when called with a block" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @output = open("|date /t") { |f| f.read } + end + @output.should_not == '' end - @output.should_not == '' end - end - ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19630 - it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" - -> { - open(cmd) { |f| f.read } - }.should complain(/Kernel#open with a leading '\|'/) + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + open(cmd) { |f| f.read } + }.should complain(/Kernel#open with a leading '\|'/) + end end end diff --git a/test/prism/fixtures/unary_method_calls.txt b/test/prism/fixtures/unary_method_calls.txt new file mode 100644 index 00000000000000..dda85e4bdbf6f9 --- /dev/null +++ b/test/prism/fixtures/unary_method_calls.txt @@ -0,0 +1,2 @@ +42.~@ +42.!@ diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb index 016fda91f03819..3104369d3eadd3 100644 --- a/test/prism/ruby/parser_test.rb +++ b/test/prism/ruby/parser_test.rb @@ -109,6 +109,9 @@ class ParserTest < TestCase # Regex with \c escape "unescaping.txt", "seattlerb/regexp_esc_C_slash.txt", + + # https://github.com/whitequark/parser/issues/1084 + "unary_method_calls.txt", ] # These files are failing to translate their lexer output into the lexer diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb index 7640ddaf1ca6c0..42a888be820924 100644 --- a/test/prism/ruby/ruby_parser_test.rb +++ b/test/prism/ruby/ruby_parser_test.rb @@ -57,6 +57,7 @@ class RubyParserTest < TestCase "spanning_heredoc.txt", "symbols.txt", "tilde_heredocs.txt", + "unary_method_calls.txt", "unparser/corpus/literal/literal.txt", "while.txt", "whitequark/cond_eflipflop.txt", diff --git a/vm_eval.c b/vm_eval.c index 71656f5a0f2ab1..b791cd4990b00f 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1669,6 +1669,24 @@ get_eval_default_path(void) return eval_default_path; } +static inline int +compute_isolated_depth_from_ep(const VALUE *ep) +{ + int depth = 1; + while (1) { + if (VM_ENV_FLAGS(ep, VM_ENV_FLAG_ISOLATED)) return depth; + if (VM_ENV_LOCAL_P(ep)) return 0; + ep = VM_ENV_PREV_EP(ep); + depth++; + } +} + +static inline int +compute_isolated_depth_from_block(const struct rb_block *blk) +{ + return compute_isolated_depth_from_ep(vm_block_ep(blk)); +} + static const rb_iseq_t * pm_eval_make_iseq(VALUE src, VALUE fname, int line, const struct rb_block *base_block) @@ -1677,8 +1695,8 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, const rb_iseq_t *iseq = parent; VALUE name = rb_fstring_lit(""); - // Conditionally enable coverage depending on the current mode: int coverage_enabled = ((rb_get_coverage_mode() & COVERAGE_TARGET_EVAL) != 0) ? 1 : 0; + int isolated_depth = compute_isolated_depth_from_block(base_block); if (!fname) { fname = rb_source_location(&line); @@ -1872,7 +1890,7 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, #undef FORWARDING_ALL_STR int error_state; - iseq = pm_iseq_new_eval(&result.node, name, fname, Qnil, line, parent, 0, &error_state); + iseq = pm_iseq_new_eval(&result.node, name, fname, Qnil, line, parent, isolated_depth, &error_state); pm_scope_node_t *prev = result.node.previous; while (prev) { @@ -1908,27 +1926,9 @@ eval_make_iseq(VALUE src, VALUE fname, int line, rb_iseq_t *iseq = NULL; VALUE ast_value; rb_ast_t *ast; - int isolated_depth = 0; - // Conditionally enable coverage depending on the current mode: int coverage_enabled = (rb_get_coverage_mode() & COVERAGE_TARGET_EVAL) != 0; - - { - int depth = 1; - const VALUE *ep = vm_block_ep(base_block); - - while (1) { - if (VM_ENV_FLAGS(ep, VM_ENV_FLAG_ISOLATED)) { - isolated_depth = depth; - break; - } - else if (VM_ENV_LOCAL_P(ep)) { - break; - } - ep = VM_ENV_PREV_EP(ep); - depth++; - } - } + int isolated_depth = compute_isolated_depth_from_block(base_block); if (!fname) { fname = rb_source_location(&line); diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 12d226ce51bea2..bd3409ff07ce5c 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -231,7 +231,9 @@ pub fn init() -> Annotations { annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); - annotate_builtin!(rb_mKernel, "class", types::Class, leaf); + // TODO(max): Annotate rb_mKernel#class as returning types::Class. Right now there is a subtle + // type system bug that causes an issue if we make it return types::Class. + annotate_builtin!(rb_mKernel, "class", types::HeapObject, leaf); annotate_builtin!(rb_mKernel, "frozen?", types::BoolExact); annotate_builtin!(rb_cSymbol, "name", types::StringExact); annotate_builtin!(rb_cSymbol, "to_s", types::StringExact); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index c7764bd290ca57..d697065da99848 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2358,7 +2358,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) IncrCounter inline_iseq_optimized_send_count - v26:Class = InvokeBuiltin leaf _bi20, v21 + v26:HeapObject = InvokeBuiltin leaf _bi20, v21 CheckInterrupts Return v26 "); @@ -7050,4 +7050,88 @@ mod hir_opt_tests { Return v19 "); } + + #[test] + fn test_fold_self_class_respond_to_true() { + eval(r#" + class C + class << self + attr_accessor :_lex_actions + private :_lex_actions, :_lex_actions= + end + self._lex_actions = [1, 2, 3] + def initialize + if self.class.respond_to?(:_lex_actions, true) + :CORRECT + else + :oh_no_wrong + end + end + end + C.new # warm up + TEST = C.instance_method(:initialize) + "#); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn initialize@:9: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + PatchPoint MethodRedefined(C@0x1000, class@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v40:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] + IncrCounter inline_iseq_optimized_send_count + v43:HeapObject = InvokeBuiltin leaf _bi20, v40 + v12:StaticSymbol[:_lex_actions] = Const Value(VALUE(0x1038)) + v13:TrueClass = Const Value(true) + PatchPoint MethodRedefined(Class@0x1040, respond_to?@0x1048, cme:0x1050) + PatchPoint NoSingletonClass(Class@0x1040) + v47:ModuleSubclass[class_exact*:Class@VALUE(0x1040)] = GuardType v43, ModuleSubclass[class_exact*:Class@VALUE(0x1040)] + PatchPoint MethodRedefined(Class@0x1040, _lex_actions@0x1078, cme:0x1080) + PatchPoint NoSingletonClass(Class@0x1040) + v51:TrueClass = Const Value(true) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + v22:StaticSymbol[:CORRECT] = Const Value(VALUE(0x10a8)) + CheckInterrupts + Return v22 + "); + } + + #[test] + fn test_fold_self_class_name() { + eval(r#" + class C; end + def test(o) = o.class.name + test(C.new) + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(C@0x1000, class@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(C@0x1000) + v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + IncrCounter inline_iseq_optimized_send_count + v27:HeapObject = InvokeBuiltin leaf _bi20, v24 + PatchPoint MethodRedefined(Class@0x1038, name@0x1040, cme:0x1048) + PatchPoint NoSingletonClass(Class@0x1038) + v31:ModuleSubclass[class_exact*:Class@VALUE(0x1038)] = GuardType v27, ModuleSubclass[class_exact*:Class@VALUE(0x1038)] + IncrCounter inline_cfunc_optimized_send_count + v33:StringExact|NilClass = CCall name@0x1070, v31 + CheckInterrupts + Return v33 + "); + } } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index df14ba5a7c2026..32c71dc5d354f4 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2712,9 +2712,9 @@ pub mod hir_build_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:Class = InvokeBuiltin leaf _bi20, v6 + v11:HeapObject = InvokeBuiltin leaf _bi20, v6 Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:Class): + bb3(v13:BasicObject, v14:HeapObject): CheckInterrupts Return v14 ");