diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index 769d06db204527..8f538825d6938f 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -73,7 +73,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index 9cb8cf0f2e1ad4..258cbedfb3df76 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 9ac48df2406118..87d222a1893d43 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -48,7 +48,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 4a0dce7847673d..03205013213305 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -38,7 +38,7 @@ jobs: with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: 4.0 diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 63ee1909bbc06c..43c4be167a7b7f 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -42,7 +42,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index a91b6a6f6e89ac..0e74a9affd441a 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -23,7 +23,7 @@ jobs: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: head diff --git a/.github/workflows/check_sast.yml b/.github/workflows/check_sast.yml index 7192f878810607..bc044a9dbb2f4f 100644 --- a/.github/workflows/check_sast.yml +++ b/.github/workflows/check_sast.yml @@ -112,7 +112,7 @@ jobs: output: sarif-results - name: filter-sarif - uses: advanced-security/filter-sarif@f3b8118a9349d88f7b1c0c488476411145b6270d # v1.0.1 + uses: advanced-security/filter-sarif@2da736ff05ef065cb2894ac6892e47b5eac2c3c0 # v1.1.0.1.1 with: patterns: | +**/*.rb diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index 997e3e8617b7ec..5d930c89438103 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -62,7 +62,7 @@ jobs: uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 77d7a8ea07208a..0c831e8f6e0658 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -59,7 +59,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 71eca66d83efe1..a2bbd70ce413c0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: 3.3.4 diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index f944e92a2ae199..d2d788df699c38 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -49,7 +49,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index f09c8ce88f153f..3f38db88c93827 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -36,7 +36,7 @@ jobs: with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index ac9f8187efa771..77792ff00d4ad9 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -70,7 +70,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index a25348a56407c5..9eb7976a613832 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -99,7 +99,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 302c6e81cf638c..9b0619421f8632 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -59,7 +59,7 @@ jobs: - run: md build working-directory: - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: # windows-11-arm has only 3.4.1, 3.4.2, 3.4.3, head ruby-version: ${{ !endsWith(matrix.os, 'arm') && '3.1' || '3.4' }} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 4e2ccd980831e4..6cbdc12fb21028 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -133,7 +133,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 20ef3a62654371..acfb4a217c6099 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -114,7 +114,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 + - uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1.292.0 with: ruby-version: '3.1' bundler: none diff --git a/file.c b/file.c index 193dbea10dd7f1..34c23e04e3a40e 100644 --- a/file.c +++ b/file.c @@ -5269,10 +5269,10 @@ rb_file_s_extname(VALUE klass, VALUE fname) } /* - * call-seq: + * call-seq: * File.path(path) -> string * - * Returns the string representation of the path + * Returns the string representation of the path * * File.path(File::NULL) #=> "/dev/null" * File.path(Pathname.new("/tmp")) #=> "/tmp" diff --git a/lib/pathname.rb b/lib/pathname.rb index b19e379cd48d9b..78c440416ea060 100644 --- a/lib/pathname.rb +++ b/lib/pathname.rb @@ -55,11 +55,35 @@ def rmtree(noop: nil, verbose: nil, secure: nil) end class Pathname # * tmpdir * - # Creates a tmp directory and wraps the returned path in a Pathname object. + # call-seq: + # Pathname.mktmpdir -> new_pathname + # Pathname.mktmpdir {|pathname| ... } -> object # - # Note that you need to require 'pathname' to use this method. + # Creates: + # + # - A temporary directory via Dir.mktmpdir. + # - A \Pathname object that contains the path to that directory. + # + # With no block given, returns the created pathname; + # the caller should delete the created directory when it is no longer needed + # (FileUtils.rm_r is a convenient method for the deletion): + # + # pathname = Pathname.mktmpdir + # dirpath = pathname.to_s + # Dir.exist?(dirpath) # => true + # # Do something with the directory. + # require 'fileutils' + # FileUtils.rm_r(dirpath) + # + # With a block given, calls the block with the created pathname; + # on block exit, automatically deletes the created directory and all its contents; + # returns the block's exit value: # - # See Dir.mktmpdir + # pathname = Pathname.mktmpdir do |p| + # # Do something with the directory. + # p + # end + # Dir.exist?(pathname.to_s) # => false def self.mktmpdir require 'tmpdir' unless defined?(Dir.mktmpdir) if block_given? diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb index 12bfe3a834b703..18e612bc1b64ff 100644 --- a/lib/rubygems/commands/owner_command.rb +++ b/lib/rubygems/commands/owner_command.rb @@ -75,7 +75,7 @@ def show_owners(name) end with_response response do |resp| - owners = Gem::SafeYAML.load clean_text(resp.body) + owners = Gem::SafeYAML.safe_load clean_text(resp.body) say "Owners for gem: #{name}" owners.each do |owner| diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index bd66aa258dad22..19718829fccbdd 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -47,7 +47,7 @@ class Gem::ConfigFile DEFAULT_CONCURRENT_DOWNLOADS = 8 DEFAULT_CERT_EXPIRATION_LENGTH_DAYS = 365 DEFAULT_IPV4_FALLBACK_ENABLED = false - DEFAULT_INSTALL_EXTENSION_IN_LIB = false + DEFAULT_INSTALL_EXTENSION_IN_LIB = true DEFAULT_GLOBAL_GEM_CACHE = false DEFAULT_USE_PSYCH = false diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb index c59b4653586282..f4bba001365fea 100644 --- a/lib/rubygems/safe_yaml.rb +++ b/lib/rubygems/safe_yaml.rb @@ -48,21 +48,8 @@ def self.safe_load(input) end end - def self.load(input) - if Gem.use_psych? - if ::Psych.respond_to?(:unsafe_load) - ::Psych.unsafe_load(input) - else - ::Psych.load(input) - end - else - Gem::YAMLSerializer.load( - input, - permitted_classes: PERMITTED_CLASSES, - permitted_symbols: PERMITTED_SYMBOLS, - aliases: aliases_enabled? - ) - end + class << self + alias_method :load, :safe_load end end end diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index 0fcb635394c433..8ab82ee4a9a1b9 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -476,6 +476,7 @@ def validate_extensions # :nodoc: validate_rake_extensions(builder) validate_rust_extensions(builder) + validate_extension_require_relative end def validate_rust_extensions(builder) # :nodoc: @@ -496,6 +497,33 @@ def validate_rake_extensions(builder) # :nodoc: WARNING end + def validate_extension_require_relative # :nodoc: + return unless @specification.extensions.any? + + require_paths = @specification.require_paths + + @specification.files.each do |rb_file| + next unless rb_file.end_with?(".rb") + next unless require_paths.any? {|rp| rb_file.start_with?("#{rp}/") } + next unless File.file?(rb_file) + + File.foreach(rb_file).with_index(1) do |line, lineno| + next unless line =~ /^\s*require_relative\s+["']([^"']+)["']/ + + required_path = Regexp.last_match(1) + resolved = File.join(File.dirname(rb_file), required_path) + + next if @specification.files.any? {|f| f == "#{resolved}.rb" || f == resolved } + + warning <<~WARNING + #{rb_file}:#{lineno} uses `require_relative "#{required_path}"` to load a compiled extension. + This will break in RubyGems 4.2, which will stop copying compiled extensions into the gem's lib directory. + Use `require` instead of `require_relative` to load compiled extensions. + WARNING + end + end + end + def validate_unique_links links = @specification.metadata.slice(*METADATA_LINK_KEYS) grouped = links.group_by {|_key, uri| uri } diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 1721519c153fea..da6e72f5936e45 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -132,6 +132,8 @@ def parse_sequence_item(content, indent) @lines.any? && current_indent > indent ? parse_node(indent) : nil elsif content.start_with?("!ruby/object:") parse_tagged_content(content.strip, indent) + elsif content.start_with?("!binary ") + parse_binary_value(content, indent) elsif content.start_with?("-") @lines.unshift("#{" " * (indent + 2)}#{content}") parse_node(indent) @@ -156,6 +158,8 @@ def parse_mapping(indent, anchor) @lines.shift val = strip_comment($2.to_s.strip) + key = decode_binary_tag(key) if key.start_with?("!binary ") + val_anchor, val = consume_value_anchor(val) value = parse_mapping_value(val, indent) value = register_anchor(val_anchor, value) if val_anchor @@ -170,6 +174,8 @@ def parse_mapping_value(val, indent) parse_inline_alias(val) elsif val.start_with?("!ruby/object:") parse_tagged_content(val.strip, indent) + elsif val.start_with?("!binary ") + parse_binary_value(val, indent) elsif val.empty? next_stripped = nil next_indent = nil @@ -306,6 +312,22 @@ def coerce(val) end end + def decode_binary_tag(str) + content = str.sub(/\A!binary\s+/, "") + content = $1 if content =~ /\A"(.*)"\z/ || content =~ /\A'(.*)'\z/ + content.unpack1("m") + end + + def parse_binary_value(val, indent) + rest = val.sub(/\A!binary\s+/, "") + if rest.start_with?("|") + content = parse_block_scalar(indent, rest[1..].to_s.strip) + Scalar.new(value: content.unpack1("m")) + else + Scalar.new(value: decode_binary_tag(val)) + end + end + def parse_alias_ref AliasRef.new(name: @lines.shift.lstrip[1..].strip) end diff --git a/prism/prism.c b/prism/prism.c index d196c5d7c4c919..9d58bdb43d2eb4 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -8594,6 +8594,7 @@ escape_write_escape_encoded(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_ } if (width == 1) { + if (*parser->current.end == '\n') pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1); escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(*parser->current.end++, flags)); } else if (width > 1) { // Valid multibyte character. Just ignore escape. @@ -8910,6 +8911,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre return; } + if (peeked == '\n') pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1); parser->current.end++; escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); return; @@ -8968,6 +8970,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre return; } + if (peeked == '\n') pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1); parser->current.end++; escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); return; @@ -9021,6 +9024,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre return; } + if (peeked == '\n') pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 1); parser->current.end++; escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_META)); return; @@ -9028,6 +9032,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre } case '\r': { if (peek_offset(parser, 1) == '\n') { + pm_line_offset_list_append(&parser->line_offsets, PM_TOKEN_END(parser, &parser->current) + 2); parser->current.end += 2; escape_write_byte_encoded(parser, buffer, flags, escape_byte('\n', flags)); return; diff --git a/prism/regexp.c b/prism/regexp.c index 93711d6b947f61..f864e187c9ca13 100644 --- a/prism/regexp.c +++ b/prism/regexp.c @@ -430,15 +430,6 @@ typedef enum { PM_REGEXP_PROPERTY_UNICODE } pm_regexp_property_type_t; -/** - * Check if a property name matches a NUL-terminated target string - * (case-insensitive, exact length match). - */ -static inline bool -pm_regexp_property_name_matches(const uint8_t *name, size_t length, const char *target) { - return strlen(target) == length && pm_strncasecmp(name, (const uint8_t *) target, length) == 0; -} - /** * Classify a property name. The name may start with '^' for negation, which * is skipped before matching. @@ -451,30 +442,63 @@ pm_regexp_classify_property(const uint8_t *name, size_t length) { length--; } - // POSIX properties — valid in all encodings. - static const char *const posix_properties[] = { - "Alnum", "Alpha", "ASCII", "Blank", "Cntrl", "Digit", "Graph", - "Lower", "Print", "Punct", "Space", "Upper", "XDigit", "Word", - NULL - }; +#define PM_REGEXP_CASECMP(str_) (pm_strncasecmp(name, (const uint8_t *) (str_), length) == 0) - for (const char *const *property = posix_properties; *property != NULL; property++) { - if (pm_regexp_property_name_matches(name, length, *property)) { - return PM_REGEXP_PROPERTY_POSIX; - } + switch (length) { + case 3: + if (PM_REGEXP_CASECMP("Han")) return PM_REGEXP_PROPERTY_SCRIPT; + break; + case 4: + if (PM_REGEXP_CASECMP("Word")) return PM_REGEXP_PROPERTY_POSIX; + break; + case 5: + /* Most properties are length 5, so dispatch on first character. */ + switch (name[0] | 0x20) { + case 'a': + if (PM_REGEXP_CASECMP("Alnum")) return PM_REGEXP_PROPERTY_POSIX; + if (PM_REGEXP_CASECMP("Alpha")) return PM_REGEXP_PROPERTY_POSIX; + if (PM_REGEXP_CASECMP("ASCII")) return PM_REGEXP_PROPERTY_POSIX; + break; + case 'b': + if (PM_REGEXP_CASECMP("Blank")) return PM_REGEXP_PROPERTY_POSIX; + break; + case 'c': + if (PM_REGEXP_CASECMP("Cntrl")) return PM_REGEXP_PROPERTY_POSIX; + break; + case 'd': + if (PM_REGEXP_CASECMP("Digit")) return PM_REGEXP_PROPERTY_POSIX; + break; + case 'g': + if (PM_REGEXP_CASECMP("Graph")) return PM_REGEXP_PROPERTY_POSIX; + if (PM_REGEXP_CASECMP("Greek")) return PM_REGEXP_PROPERTY_SCRIPT; + break; + case 'l': + if (PM_REGEXP_CASECMP("Lower")) return PM_REGEXP_PROPERTY_POSIX; + if (PM_REGEXP_CASECMP("Latin")) return PM_REGEXP_PROPERTY_SCRIPT; + break; + case 'p': + if (PM_REGEXP_CASECMP("Print")) return PM_REGEXP_PROPERTY_POSIX; + if (PM_REGEXP_CASECMP("Punct")) return PM_REGEXP_PROPERTY_POSIX; + break; + case 's': + if (PM_REGEXP_CASECMP("Space")) return PM_REGEXP_PROPERTY_POSIX; + break; + case 'u': + if (PM_REGEXP_CASECMP("Upper")) return PM_REGEXP_PROPERTY_POSIX; + break; + } + break; + case 6: + if (PM_REGEXP_CASECMP("XDigit")) return PM_REGEXP_PROPERTY_POSIX; + break; + case 8: + if (PM_REGEXP_CASECMP("Hiragana")) return PM_REGEXP_PROPERTY_SCRIPT; + if (PM_REGEXP_CASECMP("Katakana")) return PM_REGEXP_PROPERTY_SCRIPT; + if (PM_REGEXP_CASECMP("Cyrillic")) return PM_REGEXP_PROPERTY_SCRIPT; + break; } - // Script properties — valid in /e, /s, /u but not /n. - static const char *const script_properties[] = { - "Hiragana", "Katakana", "Han", "Latin", "Greek", "Cyrillic", - NULL - }; - - for (const char *const *property = script_properties; *property != NULL; property++) { - if (pm_regexp_property_name_matches(name, length, *property)) { - return PM_REGEXP_PROPERTY_SCRIPT; - } - } +#undef PM_REGEXP_CASECMP // Everything else is Unicode-only (general categories, other scripts, etc.). return PM_REGEXP_PROPERTY_UNICODE; diff --git a/test/prism/newline_offsets_test.rb b/test/prism/newline_offsets_test.rb index 99b808b1df6571..bb06876a967076 100644 --- a/test/prism/newline_offsets_test.rb +++ b/test/prism/newline_offsets_test.rb @@ -8,15 +8,38 @@ class NewlineOffsetsTest < TestCase define_method(fixture.test_name) { assert_newline_offsets(fixture) } end + def test_escape_control_newline + # Newlines consumed inside escape sequences like \C-, \c, and \M- + # must be tracked in line offsets across all literal types. + %w[\\C- \\c \\M-].each do |escape| + assert_newline_offsets_for("\"#{escape}\n\"", "#{escape} in string") + assert_newline_offsets_for("`#{escape}\n`", "#{escape} in xstring") + assert_newline_offsets_for("/#{escape}\n/", "#{escape} in regexp") + assert_newline_offsets_for("%Q{#{escape}\n}", "#{escape} in %Q") + assert_newline_offsets_for("%W[#{escape}\n]", "#{escape} in %W") + assert_newline_offsets_for("<<~H\n#{escape}\n\nH\n", "#{escape} in heredoc") + assert_newline_offsets_for("?#{escape}\n", "#{escape} in char literal") + end + + # Combined meta + control escapes + assert_newline_offsets_for("\"\\M-\\C-\n\"", "\\M-\\C- in string") + assert_newline_offsets_for("\"\\M-\\c\n\"", "\\M-\\c in string") + + # \r\n consumed inside escape context + assert_newline_offsets_for("\"\\C-\r\n\"", "\\C- with \\r\\n") + end + private def assert_newline_offsets(fixture) - source = fixture.read + assert_newline_offsets_for(fixture.read) + end + def assert_newline_offsets_for(source, message = nil) expected = [0] source.b.scan("\n") { expected << $~.offset(0)[0] + 1 } - assert_equal expected, Prism.parse(source).source.offsets + assert_equal expected, Prism.parse(source).source.offsets, message end end end diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index 783818b6eb6f52..5f5f2e03b10143 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -735,10 +735,10 @@ def write_dummy_extconf(gem_name) end ## - # Load a YAML string, the psych 3 way + # Load a YAML string using the safe loader with gem-spec permitted classes. def load_yaml(yaml) - Gem::SafeYAML.load(yaml) + Gem::SafeYAML.safe_load(yaml) end ## diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb index 8cd40e0eedde14..08df696616b8db 100644 --- a/test/rubygems/test_gem_commands_owner_command.rb +++ b/test/rubygems/test_gem_commands_owner_command.rb @@ -57,10 +57,6 @@ def test_show_owners def test_show_owners_dont_load_objects Gem.load_yaml - # Gem::SafeYAML.load uses Psych.unsafe_load when Psych is enabled, - # which does not restrict classes. Only YAMLSerializer restricts object tags. - pend "Gem::SafeYAML.load uses Psych.unsafe_load which does not restrict classes" if Gem.use_psych? - response = <