diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index 5991165d43abf3..472140469d2127 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -73,7 +73,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.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 bb84a51573814b..9fcf191799a7f0 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -29,7 +29,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 9e7720f659fcff..1fc4f025b2aaa6 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -48,7 +48,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index d329ee9b4baccf..af9e83b15369e7 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@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: 4.0 diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index a120dde7e5c959..3f2e9a9f99a891 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@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index cb1642b9e2e3b2..bcd1636bb78090 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@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: head diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index 218127aad7c1d5..ae8524e87a0278 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@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 7c26e87e57317f..816cb52007f773 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@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5d4a31d287f03c..831868e71b039d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,7 +22,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: 3.3.4 diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 39714b13a4304a..6172727ae2d854 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@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.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 3aaae5864fcaaf..f47c474f41be1a 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -39,7 +39,7 @@ jobs: with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/tarball-ubuntu.yml b/.github/workflows/tarball-ubuntu.yml index 0482db3c7f3181..35b282dc6099cf 100644 --- a/.github/workflows/tarball-ubuntu.yml +++ b/.github/workflows/tarball-ubuntu.yml @@ -43,7 +43,7 @@ jobs: set -x sudo apt-get update -q sudo apt-get install --no-install-recommends -q -y build-essential libssl-dev libyaml-dev zlib1g-dev libffi-dev libgmp-dev bison- autoconf- - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.2' # test-bundled-gems requires executable host ruby diff --git a/.github/workflows/tarball-windows.yml b/.github/workflows/tarball-windows.yml index a66cdf729d0141..d03d00f594d383 100644 --- a/.github/workflows/tarball-windows.yml +++ b/.github/workflows/tarball-windows.yml @@ -47,7 +47,7 @@ jobs: - run: md build working-directory: - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.2' bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index c887ae38118e42..7859f6610a0214 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -70,7 +70,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index f0263de5ef15e9..9a4bb715b1ce7c 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -65,7 +65,7 @@ jobs: sparse-checkout: /.github persist-credentials: false - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 03e75ad445ad9c..275546422300af 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -58,7 +58,7 @@ jobs: - run: md build working-directory: - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.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 ab816940f4bcca..6a5374d15e7ed5 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@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 707e50e36b8028..7aaa1126926aa9 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -93,7 +93,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@4bc351f7f2614e48088386e2a0ad917ca3a7e4ba # v2.81.5 + - uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154 # v2.81.8 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 1c3e3f6531633a..c33f843d687d35 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -114,12 +114,12 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 + - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 with: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@4bc351f7f2614e48088386e2a0ad917ca3a7e4ba # v2.81.5 + - uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154 # v2.81.8 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/array.c b/array.c index db4c2c4802dbaa..42aba64abed1e8 100644 --- a/array.c +++ b/array.c @@ -637,17 +637,8 @@ ary_ensure_room_for_push(VALUE ary, long add_len) * call-seq: * freeze -> self * - * Freezes +self+ (if not already frozen); returns +self+: - * - * a = [] - * a.frozen? # => false - * a.freeze - * a.frozen? # => true - * - * No further changes may be made to +self+; - * raises FrozenError if a change is attempted. - * - * Related: Kernel#frozen?. + * Freezes +self+, preventing further modifications; + * see {Frozen Objects}[rdoc-ref:frozen_objects.md]. */ VALUE diff --git a/doc/file/filename_globbing.md b/doc/file/filename_globbing.md index ce4549bffee89f..9daad17fbd3b22 100644 --- a/doc/file/filename_globbing.md +++ b/doc/file/filename_globbing.md @@ -40,9 +40,12 @@ see the table above. A simple string matches itself: ```ruby -Dir.glob('LEGAL') # => ["LEGAL"] -Dir.glob('LEGA') # => [] # Must be exact. -Dir.glob('legal') # => [] # Case-sensitive. +Dir.glob('LEGAL') # => ["LEGAL"] +Dir.glob('LEGA') # => [] # Must be exact. +Dir.glob('legal') # => [] # Case-sensitive. +Pathname('.').glob('LEGAL') # => [#] +Pathname('.').glob('LEGA') # => [] # Must be exact. +Pathname('.').glob('legal') # => [] # Case-sensitive. ``` Note that case-sensitivity may _not_ be modified by flags. @@ -50,25 +53,37 @@ Note that case-sensitivity may _not_ be modified by flags. By default, the Windows short name pattern is disabled: ```ruby -Dir.glob('PROGRAM~1') # => [] +Dir.glob('PROGRAM~1') # => [] +Pathname('.').glob('PROGRAM~1') # => [] ``` It may be enabled by flag [`File::FNM_SHORTNAME`](#constant-filefnmshortname). - ### Any Sequence of Characters (`'*'`) The asterisk pattern (`'*'`) matches any sequence of characters: ```ruby Dir.glob('*').take(3) # => ["BSDL", "CONTRIBUTING.md", "COPYING"] -Dir.glob('\*') # => [] # Escaped. +Pathname('.').glob('*').take(3) +# => +# #, +# #, +# #] +``` + +The pattern may be escaped: + +```ruby +Dir.glob('\*') # => [] +Pathname('.').glob('\*') # => [] ``` By default, the asterisk pattern does not match a leading period (as in a dot-file): ```ruby -Dir.glob('*').select {|entry| entry.start_with?('.') } # => [] +Dir.glob('*').select {|entry| entry.start_with?('.') } # => [] +Pathname('.').glob('*') .select {|pn| pn.to_s.start_with?('.') } # => [] ``` That matching may be enabled by flag [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch). @@ -76,7 +91,8 @@ That matching may be enabled by flag [`File::FNM_DOTMATCH`](#constant-filefnmdot The asterisk pattern does not match across file separators: ```ruby -Dir.glob('*.rb').select {|entry| entry.include?('/') } # => [] +Dir.glob('*.rb').select {|entry| entry.include?('/') } # => [] +Pathname('.').glob('*.rb') .select {|pn| pn.to_s.include?('/') } # => [] ``` Therefore flag File::FNM_PATHNAME does not affect the pattern. @@ -87,16 +103,22 @@ The question-mark pattern (`'?'`) matches any single character: ```ruby Dir.glob('???') # => ["GPL", "bin", "doc", "enc", "ext", "jit", "lib", "man"] -Dir.glob('??') # => ["gc"] # Only one entry with a 2-character name. -Dir.glob('?') # => [] # No entries with a 1-character name. -Dir.glob('\?') # => [] # No entries containing character '?'. +Dir.glob('??') # => ["gc"] # Only one entry with a 2-character name. +Dir.glob('?') # => [] # No entries with a 1-character name. +Dir.glob('\?') # => [] # No entries containing character '?'. +Pathname('.').glob('???').take(3) # => [#, #, #] +Pathname('.').glob('??') # => [#] # Only one entry with a 2-character name. +Pathname('.').glob('?') # => [] # No entries with a 1-character name. +Pathname('.').glob('\?') # => [] # No entries containing character '?'. ``` By default, the question-mark pattern does not match a leading period (as in a dot-file): ```ruby -Dir.glob(".???") # => [".git"] -Dir.glob("????").select {|entry| entry.start_with?('.') } # => [] +Dir.glob(".???") # => [".git"] +Dir.glob("????").select {|entry| entry.start_with?('.') } # => [] +Pathname('.').glob('.???') # => [#] +Pathname('.').glob('????').select {|pn| pn.to_s.start_with?('.') } # => [] ``` That matching may be enabled by flag [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch). @@ -107,14 +129,22 @@ Characters enclosed in square brackets define a set of characters, any of which matches a single character: ```ruby -Dir.glob('[efgh][abcd]') # => ["gc"] -Dir.glob('\[efgh][abcd]') # => [] # Escaped. +Dir.glob('[efgh][abcd]') # => ["gc"] +Pathname('.').glob('[efgh][abcd]') # => [#] +``` + +The pattern may be escaped: + +```ruby +Dir.glob('\[efgh][abcd]') # => [] +Pathname('.').glob('\[efgh][abcd]') # => [] ``` The character set may be negated: ```ruby -Dir.glob('[^abcd][^efgh]') # => ["gc"] +Dir.glob('[^abcd][^efgh]') # => ["gc"] +Pathname('.').glob('[^abcd][^efgh]') # => [#] ``` ### Single Character from a \Range (`'[a-c]'`, `'[^a-c]'`) @@ -123,15 +153,26 @@ A range of characters enclosed in square brackets defines a set of characters, any of which matches a single character: ```ruby -Dir.glob('[k-m][h-j][a-c]') # => ["lib"] -Dir.glob('\[k-m][h-j][a-c]') # => [] # Escaped. +Dir.glob('[k-m][h-j][a-c]') # => ["lib"] +Pathname('.').glob('[k-m][h-j][a-c]') # => [#] +``` + +The pattern may be escaped: + +```ruby +Dir.glob('\[k-m][h-j][a-c]') # => [] +Pathname('.').glob('\[k-m][h-j][a-c]') # => [] ``` The range may be negated: ```ruby -Dir.glob('[^k-m][h-j][a-c]') # => [] -Dir.glob('[^a-c][^k-m][^h-j]') # => ["GPL", "doc", "enc", "ext", "jit", "lib", "man"] +Dir.glob('[^k-m][h-j][a-c]') # => [] +Dir.glob('[^a-c][^k-m][^h-j]').take(3) # => ["GPL", "doc", "enc"] +Pathname('.').glob('[^k-m][h-j][a-c]') # => [] +Pathname('.').glob('[^a-c][^k-m][^h-j]').take(3) +# => [#, #, #] + ``` ### Alternatives (`'{ , }'`) @@ -140,10 +181,29 @@ The alternatives pattern consists of comma-separated strings enclosed in curly braces: ```ruby -Dir.glob('{k,L,R}*') # => ["kernel.rb", "LEGAL", "README.ja.md", "README.md"] -Dir.glob('{R,L,k}*') # => ["README.ja.md", "README.md", "LEGAL", "kernel.rb"] -# Whitespace matters: +Dir.glob('{k,L,R}*').take(3) # => ["kernel.rb", "LEGAL", "README.ja.md"] +Dir.glob('{R,L,k}*').take(3) # => ["README.ja.md", "README.md", "LEGAL"] +Pathname('.').glob('{k,L,R}*').take(3) +# # => +# [#, +# #, +# #] +Pathname('.').glob('{R,L,k}*').take(3) +# # => +# [#, +# #, +# #] +``` + +Whitespace matters: + +```ruby Dir.glob('{k ,L,R}*') # => ["LEGAL", "README.ja.md", "README.md"] +Pathname('.').glob('{k ,L,R}*') +# # => +# [#, +# #, +# #] ``` ### Recursive Directory Matching (`'**'`) @@ -152,32 +212,57 @@ The double-asterisk pattern (`'**'`) matches directories recursively: ```ruby # Find all entries everywhere ending with '.ja'. -Dir.glob('**/*.ja') -# => ["COPYING.ja", "doc/pty/README.expect.ja", "doc/pty/README.ja"] +Dir.glob('**/*.ja') # => ["COPYING.ja", "doc/pty/README.expect.ja", "doc/pty/README.ja"] +Pathname('.').glob('**/*.ja') +# # => +# [#, +# #, +# #] # Find all entries everywhere ending with '.rb'. -Dir.glob('**/*.rb').size # => 7574 -Dir.glob('**/*.rb').take(3) -# => ["KNOWNBUGS.rb", "array.rb", "ast.rb"] +Dir.glob('**/*.rb').size # => 7527 +Dir.glob('**/*.rb').take(3) # => ["KNOWNBUGS.rb", "array.rb", "ast.rb"] +Pathname('.').glob('**/*.rb').size # => 7527 +Pathname('.').glob('**/*.rb').take(3) +# # => +# [#, +# #, +# #] # Find all entries in directory 'lib' ending with `.rb'. -Dir.glob('lib/**/*.rb').size # => 626 +Dir.glob('lib/**/*.rb').size # => 621 Dir.glob('lib/**/*.rb').take(3) # # => # ["lib/English.rb", # "lib/bundled_gems.rb", # "lib/bundler/build_metadata.rb"] +Pathname('.').glob('lib/**/*.rb').size # => 621 +Pathname('.').glob('lib/**/*.rb').take(3) +# # => +# [#, +# #, +# #] # Find all entries in directory 'test/ruby' ending with '.rb'. -Dir.glob('test/ruby/**/*.rb').size # => 200 +Dir.glob('test/ruby/**/*.rb').size # => 200 Dir.glob('test/ruby/**/*.rb').take(3) # # => # ["test/ruby/allpairs.rb", # "test/ruby/beginmainend.rb", # "test/ruby/box/a.1_1_0.rb"] +Pathname('.').glob('test/ruby/**/*.rb').size # => 200 +Pathname('.').glob('test/ruby/**/*.rb').take(3) +# # => +# [#, +# #, +# #] +``` -# Escaped. -Dir.glob('\**/*.rb') # => [] +The pattern may be escaped: + +```ruby +Dir.glob('\**/*.rb') # => [] +Pathname('.').glob('\**/*.rb') # => [] ``` @@ -187,11 +272,16 @@ The backslash character (`'\'`) may be used to escape any of the characters that filename globbing treats as special: ```ruby -Dir.glob('\*') # => [] -Dir.glob('\?') # => [] -Dir.glob('\[efgh][abcd]') # => [] -Dir.glob('\[k-m][h-j][a-c]') # => [] -Dir.glob('\**/*.rb') # => [] +Dir.glob('\*') # => [] +Dir.glob('\?') # => [] +Dir.glob('\[efgh][abcd]') # => [] +Dir.glob('\[k-m][h-j][a-c]') # => [] +Dir.glob('\**/*.rb') # => [] +Pathname('.').glob('\*') # => [] +Pathname('.').glob('\?') # => [] +Pathname('.').glob('\[efgh][abcd]') # => [] +Pathname('.').glob('\[k-m][h-j][a-c]') # => [] +Pathname('.').glob('\**/*.rb') # => [] ``` ## Keyword Arguments @@ -205,20 +295,23 @@ Dir.glob('\**/*.rb') # => [] ### `base` Optional keyword argument `base` (defaults to `'.'`) -specifies where in the filesystem the searching is to begin: +specifies (for `Dir.glob`) where in the filesystem the searching is to begin; +the argument is ignored for `Pathname#glob`, whose "base" is its string path: ```ruby -Dir.glob('*').size # => 241 +# Default base '.'. Dir.glob('*').take(3) # => ["BSDL", "CONTRIBUTING.md", "COPYING"] - -Dir.glob('*', base: 'lib').size # => 72 Dir.glob('*', base: 'lib').take(3) # => ["English.gemspec", "English.rb", "bundled_gems.rb"] - -Dir.glob('*', base: 'lib/net').size # => 5 Dir.glob('*', base: 'lib/net').take(3) # => ["http", "http.rb", "https.rb"] +Pathname('.').glob('*').take(3) +# => [#, #, #] +Pathname('lib').glob('*').take(3) +# => [#, #, #] +Pathname('lib/net').glob('*').take(3) +# => [#, #, #] ``` ### `flags` @@ -249,15 +342,21 @@ These constants do not affect filename globbing: #### Constant File::FNM_DOTMATCH -By default, filename globbing does not allow patterns `'*'` and `'?'` to match a dotfile name +By default, filename globbing does not allow patterns `'*'` and `'?'` +to match a dotfile name (i.e, an entry name beginning with a dot); use constant [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch) to enable the match: ```ruby -Dir.glob('*').size # => 241 -Dir.glob('*', flags: File::FNM_DOTMATCH).size # => 256 -Dir.glob('*', flags: File::FNM_DOTMATCH).take(3) # => [".", ".dir-locals.el", ".document"] +Dir.glob('*').size # => 241 +Dir.glob('*', flags: File::FNM_DOTMATCH).size # => 256 +Dir.glob('*', flags: File::FNM_DOTMATCH).take(3) +# => [".", ".dir-locals.el", ".document"] +Pathname('.').glob('*').size # => 241 +Pathname('.').glob('*', flags: File::FNM_DOTMATCH).size # => 256 +Pathname('.').glob('*', flags: File::FNM_DOTMATCH).take(3) +# => [#, #, #] ``` #### Constant File::FNM_NOESCAPE @@ -267,8 +366,12 @@ use constant [`File::FNM_NOESCAPE`](#constant-filefnmnoescape) to disable it: ```ruby -Dir.glob('*').size # => 241 -Dir.glob('\*').size # => 0 +Dir.glob('*').size # => 241 +Dir.glob('\*').size # => 0 +Dir.glob('\*', File::FNM_NOESCAPE).size # => 0 +Pathname('.').glob('*').size # => 241 +Pathname('.').glob('\*').size # => 0 +Pathname('.').glob('\*', File::FNM_NOESCAPE).size # => 0 ``` #### Constant File::FNM_SHORTNAME @@ -291,9 +394,11 @@ Optional keyword argument `sort` (defaults to `'true'`) specifies whether the returned array is to be sorted: ```ruby -Dir.glob('*').take(3) -# => ["BSDL", "CONTRIBUTING.md", "COPYING"] -Dir.glob('*', sort: false).take(3) -# => ["gc.rb", "yjit.rb", "iseq.h"] +Dir.glob('*').take(3) # => ["BSDL", "CONTRIBUTING.md", "COPYING"] +Dir.glob('*', sort: false).take(3) # => ["gc.rb", "yjit.rb", "iseq.h"] +Pathname('.').glob('*').take(3) +# => [#, #, #] +Pathname('.').glob('*', sort: false).take(3) +# => [#, #, #] ``` diff --git a/doc/file/filename_matching.md b/doc/file/filename_matching.md index fca02f1d83a824..a77501c8e78434 100644 --- a/doc/file/filename_matching.md +++ b/doc/file/filename_matching.md @@ -45,12 +45,10 @@ File.fnmatch('xyzzy', 'xyzzy') # => true File.fnmatch('one_two_three', 'one_two_three') # => true File.fnmatch('123', '123') # => true File.fnmatch('Form 27B/6', 'Form 27B/6') # => true - Pathname('xyzzy').fnmatch('xyzzy') # => true Pathname('one_two_three').fnmatch('one_two_three') # => true Pathname('123').fnmatch('123') # => true Pathname('Form 27B/6').fnmatch('Form 27B/6') # => true - # Must be exact. pattern = 'abcde' path = 'abc' @@ -103,7 +101,6 @@ pattern = '*' File.fnmatch(pattern, 'foo') # => true File.fnmatch(pattern, '') # => true File.fnmatch(pattern, 'foo') # => true - Pathname('foo').fnmatch(pattern) # => true Pathname('').fnmatch(pattern) # => true Pathname('*').fnmatch(pattern) # => true @@ -148,7 +145,6 @@ pattern = '?' File.fnmatch(pattern, 'f') # => true File.fnmatch(pattern, '') # => false File.fnmatch(pattern, 'foo') # => false - Pathname('f').fnmatch(pattern) # => true Pathname('').fnmatch(pattern) # => false Pathname('foo').fnmatch(pattern) # => false @@ -189,7 +185,6 @@ pattern = '[ruby]' File.fnmatch(pattern, 'r') # => true File.fnmatch(pattern, 'u') # => true File.fnmatch(pattern, 'y') # => true - Pathname('r').fnmatch(pattern) # => true Pathname('u').fnmatch(pattern) # => true Pathname('y').fnmatch(pattern) # => true @@ -216,7 +211,6 @@ The character set may be negated: pattern = '[^ruby]' File.fnmatch(pattern, 'r') # => false File.fnmatch(pattern, 'u') # => false - Pathname('r').fnmatch(pattern) # => false Pathname('u').fnmatch(pattern) # => false ``` @@ -231,7 +225,6 @@ pattern = '[a-c]' File.fnmatch(pattern, 'b') # => true File.fnmatch(pattern, 'd') # => false File.fnmatch(pattern, 'abc') # => false - Pathname('b').fnmatch(pattern) # => true Pathname('d').fnmatch(pattern) # => false Pathname('abc').fnmatch(pattern) # => false @@ -276,7 +269,6 @@ File.fnmatch('[a-c]', path) # => true File.fnmatch('\[a-c]', path) # => false File.fnmatch('[a-c\]', path) # => false File.fnmatch('[a\-c]', path) # => false - Pathname(path).fnmatch('[a-c]') # => true Pathname(path).fnmatch('\[a-c]') # => false Pathname(path).fnmatch('[a-c\]') # => false @@ -286,8 +278,7 @@ File.fnmatch('{a,b}', path, File::FNM_EXTGLOB) # => true File.fnmatch('\{a,b}', path, File::FNM_EXTGLOB) # => false File.fnmatch('{a\,b}', path, File::FNM_EXTGLOB) # => false File.fnmatch('{a,b\}', path, File::FNM_EXTGLOB) # => false - -Pathname(path).fnmatch('{a,b}', File::FNM_EXTGLOB) # => true +Pathname(path).fnmatch('{a,b}', File::FNM_EXTGLOB) # => true Pathname(path).fnmatch('\{a,b}', File::FNM_EXTGLOB) # => false Pathname(path).fnmatch('{a,b\}', File::FNM_EXTGLOB) # => false Pathname(path).fnmatch('{a\,b}', File::FNM_EXTGLOB) # => false @@ -332,8 +323,12 @@ use constant [`File::FNM_CASEFOLD`](#constant-filefnmcasefold) to make the matching case-insensitive: ```ruby -File.fnmatch('abc', 'ABC') # => false -File.fnmatch('abc', 'ABC', File::FNM_CASEFOLD) # => true +pattern = 'abc' +path = 'ABC' +File.fnmatch(pattern, path) # => false +File.fnmatch(pattern, path, File::FNM_CASEFOLD) # => true +Pathname(path).fnmatch(pattern) # => false +Pathname(path).fnmatch(pattern, File::FNM_CASEFOLD) # => true ``` ### Constant File::FNM_DOTMATCH @@ -344,8 +339,12 @@ use constant [`File::FNM_DOTMATCH`](#constant-filefnmdotmatch) to enable the match: ```ruby -File.fnmatch('*', '.document') # => false -File.fnmatch('*', '.document', File::FNM_DOTMATCH) # => true +pattern = '*' +path = '.document' +File.fnmatch(pattern, path) # => false +File.fnmatch(pattern, path, File::FNM_DOTMATCH) # => true +Pathname(path).fnmatch(pattern) # => false +Pathname(path).fnmatch(pattern, File::FNM_DOTMATCH) # => true ``` ### Constant File::FNM_EXTGLOB @@ -354,26 +353,50 @@ use constant [`File::FNM_EXTGLOB`](#constant-filefnmextglob) to enable it: ```ruby -File.fnmatch('R{ub,foo}y', 'Ruby') # => false -File.fnmatch('R{ub,foo}y', 'Ruby', File::FNM_EXTGLOB) # => true +pattern = 'R{ub,foo}y' +path = 'Ruby' +File.fnmatch(pattern, path) # => false +Pathname(path).fnmatch(pattern, File::FNM_EXTGLOB) # => true +Pathname(path).fnmatch(pattern) # => false +Pathname(path).fnmatch(pattern, File::FNM_EXTGLOB) # => true ``` The alternatives pattern consists of zero or more unquoted strings, separated by commas, and enclosed in curly braces: ```ruby -File.fnmatch('R{ub,foo,bar}y', 'Ruby') # => false # Not enabled. -File.fnmatch('R{ub,foo,bar}y', 'Ruby', File::FNM_EXTGLOB) # => true -# Whitespace matters. -File.fnmatch('R{ub ,foo,bar}y', 'Ruby', File::FNM_EXTGLOB) # => false -File.fnmatch('R{ ub,foo,bar}y', 'Ruby', File::FNM_EXTGLOB) # => false -# Special characters remain in force: -File.fnmatch('{*,?}', 'hello', File::FNM_EXTGLOB) # => true -File.fnmatch('{*ello,?}', 'hello', File::FNM_EXTGLOB) # => true -File.fnmatch('{*ELLO,?}', 'hello', File::FNM_EXTGLOB) # => false -File.fnmatch('{*ELLO,?????}', 'hello', File::FNM_EXTGLOB) # => true -# With the flag not given. -File.fnmatch('R{ub,foo,bar}y', 'Ruby') # => false +pattern = 'R{ub,foo,bar}y' +path = 'Ruby' +File.fnmatch(pattern, path) # => false +File.fnmatch(pattern, path, File::FNM_EXTGLOB) # => true +Pathname(path).fnmatch(pattern) # => false +Pathname(path).fnmatch(pattern, File::FNM_EXTGLOB) # => true +``` + +Whitespace matters: + +```ruby +path = 'Ruby' +pattern = 'R{ub ,foo,bar}y' +File.fnmatch(pattern, path, File::FNM_EXTGLOB) # => false +Pathname(path).fnmatch(pattern, File::FNM_EXTGLOB) # => false +pattern = 'R{ ub,foo,bar}y' +File.fnmatch(pattern, path, File::FNM_EXTGLOB) # => false +Pathname(path).fnmatch(pattern, File::FNM_EXTGLOB) # => false +``` + +Special characters remain in force: + +```ruby +path = 'hello' +File.fnmatch('{*,?}', path, File::FNM_EXTGLOB) # => true +File.fnmatch('{*ello,?}', path, File::FNM_EXTGLOB) # => true +File.fnmatch('{*ELLO,?}', path, File::FNM_EXTGLOB) # => false +File.fnmatch('{*ELLO,?????}', path, File::FNM_EXTGLOB) # => true +Pathname(path).fnmatch('{*,?}', File::FNM_EXTGLOB) # => true +Pathname(path).fnmatch('{*ello,?}', File::FNM_EXTGLOB) # => true +Pathname(path).fnmatch('{*ELLO,?}', File::FNM_EXTGLOB) # => false +Pathname(path).fnmatch('{*ELLO,?????}', File::FNM_EXTGLOB) # => true ``` ### Constant File::FNM_NOESCAPE @@ -383,8 +406,12 @@ use constant [`File::FNM_NOESCAPE`](#constant-filefnmnoescape) to disable it: ```ruby -File.fnmatch('\*\?\*\*', '*?**') # => true -File.fnmatch('\*\?\*\*', '*?**', File::FNM_NOESCAPE) # => false +pattern = '\*\?\*\*' +path = '*?**' +File.fnmatch(pattern, path) # => true +File.fnmatch(pattern, path, File::FNM_NOESCAPE) # => false +Pathname(path).fnmatch(pattern) # => true +Pathname(path).fnmatch(pattern, File::FNM_NOESCAPE) # => false ``` ### Constant File::FNM_PATHNAME @@ -396,18 +423,26 @@ By default, the double-asterisk pattern (`'**'`) is equivalent to pattern `'*'`, and matches any sequence of directory-like substrings: ```ruby -File.fnmatch('**', 'a/b/c') # => true -File.fnmatch('*', 'a/b/c') # => true +path = 'a/b/c' +File.fnmatch('**', path) # => true +File.fnmatch('*', path) # => true +Pathname(path).fnmatch('**') # => true +Pathname(path).fnmatch('*') # => true ``` When flag [`File::FNM_PATHNAME`](#constant-filefnmpathname) is given, the pattern matches only one component of a file path: ```ruby -File.fnmatch('**', 'a/b/c') # => true # Matches 'a/b/c'. -File.fnmatch('**', 'a/b/c', File::FNM_PATHNAME) # => false # Matches only 'a'. -File.fnmatch('**', 'a/b/c', File::FNM_PATHNAME) # => false # Matches only 'a/b'. -File.fnmatch('**/*', 'a/b/c', File::FNM_PATHNAME) # => true # Matches 'a/b', then 'c'. +path = 'a/b/c' +File.fnmatch('**', path) # => true # Matches 'a/b/c'. +File.fnmatch('**', path, File::FNM_PATHNAME) # => false # Matches only 'a'. +File.fnmatch('*/*', path, File::FNM_PATHNAME) # => false # Matches only 'a/b'. +File.fnmatch('**/*', path, File::FNM_PATHNAME) # => true # Matches 'a/b', then 'c'. +Pathname(path).fnmatch('**') # => true # Matches 'a/b/c'. +Pathname(path).fnmatch('**', File::FNM_PATHNAME) # => false # Matches only 'a'. +Pathname(path).fnmatch('*/*', File::FNM_PATHNAME) # => false # Matches only 'a/b'. +Pathname(path).fnmatch('**/*', File::FNM_PATHNAME) # => true # Matches 'a/b', then 'c'. ``` By default, filename matching enables pattern `'*'` to match @@ -417,8 +452,12 @@ to disable such matching: ```ruby File::SEPARATOR # => "/" -File.fnmatch('*.rb', 'lib/test.rb') # => true -File.fnmatch('*.rb', 'lib/test.rb', File::FNM_PATHNAME) # => false +pattern = '*.rb' +path = 'lib/test.rb' +File.fnmatch(pattern, path) # => true +File.fnmatch(pattern, path, File::FNM_PATHNAME) # => false +Pathname(path).fnmatch(pattern) # => true +Pathname(path).fnmatch(pattern, File::FNM_PATHNAME) # => false ``` By default, filename matching enables pattern `'?'` to match @@ -427,8 +466,12 @@ use constant [`File::FNM_PATHNAME`](#constant-filefnmpathname) to disable such matching: ```ruby -File.fnmatch('foo?boo', 'foo/boo') # => true -File.fnmatch('foo?boo', 'foo/boo', File::FNM_PATHNAME) # => false +pattern = 'foo?boo' +path = 'foo/boo' +File.fnmatch(pattern, path) # => true +File.fnmatch(pattern, path, File::FNM_PATHNAME) # => false +Pathname(path).fnmatch(pattern) # => true +Pathname(path).fnmatch(pattern, File::FNM_PATHNAME) # => false ``` ### Constant File::FNM_SHORTNAME @@ -448,10 +491,13 @@ even when dealing with files that have long names. ```ruby File::FNM_SHORTNAME.zero? # => false # On Windows, not zero; may be enabled. File::FNM_SHORTNAME.zero? # => true # Elsewhere, always zero; may not be enabled. - -File.fnmatch('PROGRAM~1', 'Program Files') # => false -# This will be true if and only if on Windows and short name 'PROGRAM~1' exists. -File.fnmatch('PROGRAM~1', 'Program Files', File::FNM_SHORTNAME) # => true +pattern = 'PROGRAM~1' +path = 'Program Files' +File.fnmatch(pattern, path) # => false +Pathname(path).fnmatch(pattern) # => false +# These will return true if and only if on Windows and short name 'PROGRAM~1' exists. +File.fnmatch(pattern, path, File::FNM_SHORTNAME) # => true +Pathname(path).fnmatch(pattern, File::FNM_SHORTNAME) # => true ``` ### Constant File::FNM_SYSCASE @@ -463,9 +509,13 @@ to use the case-sensitivity rules of the underlying file system: ```ruby File::FNM_SYSCASE.zero? # => false # On Windows, not zero; may be enabled. File::FNM_SYSCASE.zero? # => true # Elsewhere, always zero; may not be enabled. - -File.fnmatch('abc', 'ABC') # => false # Ruby; case-sensitive. -File.fnmatch('abc', 'ABC', File::FNM_SYSCASE) # => true # Windows; case-insensitive. -File.fnmatch('abc', 'ABC', File::FNM_SYSCASE) # => false # Linus; case-sensitive. +pattern = 'abc' +path = 'ABC' +File.fnmatch(pattern, path) # => false # Ruby; case-sensitive. +File.fnmatch(pattern, path, File::FNM_SYSCASE) # => true # Windows; case-insensitive. +File.fnmatch(pattern, path, File::FNM_SYSCASE) # => false # Linux; case-sensitive. +Pathname(path).fnmatch(pattern) # => false # Ruby; case-sensitive. +Pathname(path).fnmatch(pattern, File::FNM_SYSCASE) # => true # Windows; case-insensitive. +Pathname(path).fnmatch(pattern, File::FNM_SYSCASE) # => false # Linux; case-sensitive. ``` diff --git a/doc/language/frozen_objects.md b/doc/language/frozen_objects.md new file mode 100644 index 00000000000000..394b0030b4252c --- /dev/null +++ b/doc/language/frozen_objects.md @@ -0,0 +1,77 @@ +# Frozen Objects + +A Ruby object may be made immutable by freezing it with method `#freeze`, +which returns `self`; +method Kernel#frozen? returns whether an object is frozen: + +```ruby +a = %w[foo bar] # => ["foo", "bar"] +a.frozen? # => false +a << 'baz' # => ["foo", "bar", "baz"] +a.freeze # => ["foo", "bar", "baz"] +a.frozen? # => true +a << 'bat' # Raises FrozenError; can't modify frozen Array +``` + +A frozen object may not be unfrozen. + +In general, an object should be frozen if it should not be modified; +such objects may include: + +- Constants. +- Configuration objects. +- Lookup tables. +- Value objects. +- Objects shared across threads/Ractors. +- Strings in libraries. + +## Frozen Objects in Ruby + +Instances of these Ruby classes are always frozen: + +- `Symbol`. +- `Integer`. +- `Float`. +- `Rational`. +- `Complex`. +- `TrueClass`; has one instance: `true`. +- `FalseClass`; has one instance: `false`. +- `NilClass`; has one instance: `nil`. + +Other Ruby classes, including container classes (such as Array, Hash, and Set) +are by default not frozen. + +\String objects are by default not frozen, +but many Ruby libraries freeze their string objects, +commonly by placing a "magic comment" at the top of source files: + +```ruby +# frozen_string_literal: true +``` + +## Frozen User Objects + +Almost any object may be frozen: + +```ruby +KEYWORDS = %w[foo bar baz].freeze +# => ["foo", "bar", "baz"] +CONFIG = {foo: 0, bar: 1}.freeze +# => {foo: 0, bar: 1} +``` + +Freezing string objects enables deduplication, which can save memory and improve performance: + +```ruby +'foo'.object_id == 'foo'.object_id # => false # Two objects are stored. +'foo'.freeze.object_id == 'foo'.freeze.object_id # => true # Only one object is stored. +``` + +\String objects for an entire Ruby source file may be frozen via a "magic comment" +(which must be at the top of the file): + +```ruby +# frozen_string_literal: true + +'foo'.object_id == 'foo'.object_id # => true +``` diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 20eab820a16a47..775443ecf79949 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -777,12 +777,27 @@ objspace_internal_class_of(VALUE self, VALUE obj) /* * call-seq: - * ObjectSpace.internal_super_of(cls) -> Class or Module + * ObjectSpace.internal_super_of(cls) -> class or module * - * [MRI specific feature] Return internal super class of cls (Class or Module). - * obj can be an instance of InternalObjectWrapper. + * Returns the immediate superclass of +cls+, including any hidden class such + * as an included module's iclass. + * + * Unlike Class#superclass, this does not skip over the iclasses that Ruby + * inserts for included modules: + * + * require 'objspace' + * + * module M; end + * class A; include M; end + * A.superclass # => Object + * ObjectSpace.internal_super_of(A) # => # + * + * +cls+ must be a Class or Module, or an ObjectSpace::InternalObjectWrapper + * that wraps one. * * Note that you should not use this method in your application. + * + * This method is only expected to work with C Ruby. */ static VALUE objspace_internal_super_of(VALUE self, VALUE obj) diff --git a/ext/psych/lib/psych/class_loader.rb b/ext/psych/lib/psych/class_loader.rb index c8f509720af6a8..3a866ae4b4673d 100644 --- a/ext/psych/lib/psych/class_loader.rb +++ b/ext/psych/lib/psych/class_loader.rb @@ -9,6 +9,7 @@ class ClassLoader # :nodoc: DATA = 'Data' unless RUBY_VERSION < "3.2" DATE = 'Date' DATE_TIME = 'DateTime' + ENCODING = 'Encoding' EXCEPTION = 'Exception' OBJECT = 'Object' PSYCH_OMAP = 'Psych::Omap' diff --git a/ext/psych/lib/psych/visitors/to_ruby.rb b/ext/psych/lib/psych/visitors/to_ruby.rb index 475444e589d1ca..79565dfcbccfa4 100644 --- a/ext/psych/lib/psych/visitors/to_ruby.rb +++ b/ext/psych/lib/psych/visitors/to_ruby.rb @@ -72,7 +72,7 @@ def deserialize o when '!binary', 'tag:yaml.org,2002:binary' o.value.unpack('m').first when /^!(?:str|ruby\/string)(?::(.*))?$/, 'tag:yaml.org,2002:str' - klass = resolve_class($1) + klass = resolve_subclass($1, String) if klass klass.allocate.replace o.value else @@ -87,6 +87,7 @@ def deserialize o DateTime.civil(*t.to_a[0, 6].reverse, Rational(t.utc_offset, 86400)) + (t.subsec/86400) when '!ruby/encoding' + class_loader.encoding ::Encoding.find o.value when "!ruby/object:Complex" class_loader.complex @@ -156,7 +157,7 @@ def visit_Psych_Nodes_Sequence o } map when /^!(?:seq|ruby\/array):(.*)$/ - klass = resolve_class($1) + klass = resolve_subclass($1, Array) list = register(o, klass.allocate) o.children.each { |c| list.push accept c } list @@ -246,7 +247,7 @@ def visit_Psych_Nodes_Mapping o end when /^!(?:str|ruby\/string)(?::(.*))?$/, 'tag:yaml.org,2002:str' - klass = resolve_class($1) + klass = resolve_subclass($1, String) members = {} string = nil @@ -267,7 +268,7 @@ def visit_Psych_Nodes_Mapping o end init_with(string, members.map { |k,v| [k.to_s.sub(/^@/, ''),v] }, o) when /^!ruby\/array:(.*)$/ - klass = resolve_class($1) + klass = resolve_subclass($1, Array) list = register(o, klass.allocate) members = Hash[o.children.map { |c| accept c }.each_slice(2).to_a] @@ -301,7 +302,7 @@ def visit_Psych_Nodes_Mapping o set when /^!ruby\/hash-with-ivars(?::(.*))?$/ - hash = $1 ? resolve_class($1).allocate : {} + hash = $1 ? resolve_subclass($1, Hash).allocate : {} register o, hash o.children.each_slice(2) do |key, value| case key.value @@ -316,7 +317,7 @@ def visit_Psych_Nodes_Mapping o hash when /^!map:(.*)$/, /^!ruby\/hash:(.*)$/ - revive_hash register(o, resolve_class($1).allocate), o + revive_hash register(o, resolve_subclass($1, Hash).allocate), o when '!omap', 'tag:yaml.org,2002:omap' map = register(o, class_loader.psych_omap.new) @@ -468,6 +469,19 @@ def init_with o, h, node def resolve_class klassname class_loader.load klassname end + + # Resolve +klassname+ and ensure it is +parent+ or one of its + # subclasses. Tags such as !ruby/hash-with-ivars are only ever emitted + # for subclasses of a specific core class; without this check a crafted + # document could name an unrelated (but permitted) class and have its + # state populated directly, bypassing the class's own init_with. + def resolve_subclass klassname, parent + klass = resolve_class(klassname) + if klass && !(klass <= parent) + raise ArgumentError, "Invalid tag: expected a subclass of #{parent}, got #{klass}" + end + klass + end end class NoAliasRuby < ToRuby diff --git a/file.c b/file.c index fffd09c22e05a5..661232f053a7ea 100644 --- a/file.c +++ b/file.c @@ -2443,17 +2443,35 @@ rb_file_ftype(mode_t mode) /* * call-seq: - * File.ftype(file_name) -> string + * File.ftype(path) -> string * - * Identifies the type of the named file; the return string is one of - * ``file'', ``directory'', - * ``characterSpecial'', ``blockSpecial'', - * ``fifo'', ``link'', - * ``socket'', or ``unknown''. + * Returns the string type of the object at +path+, one of: * - * File.ftype("testfile") #=> "file" - * File.ftype("/dev/tty") #=> "characterSpecial" - * File.ftype("/tmp/.X11-unix/X0") #=> "socket" + * - 'file'. + * - 'directory'. + * - 'characterSpecial'. + * - 'blockSpecial'. + * - 'fifo'. + * - 'link'. + * - 'socket'. + * + * Examples: + * + * File.ftype('README.md') # => "file" + * File.ftype('lib') # => "directory" + * File.ftype("/dev/null") # => "characterSpecial" + * File.ftype("/dev/loop0") # => "blockSpecial" + * + * File.mkfifo('/tmp/pipe', 0666) + * File.ftype('/tmp/pipe') # => "fifo" + * + * File.symlink('lib', 'lib_link') + * File.ftype('lib_link') # => "link" + * + * UNIXServer.new('/tmp/socket') + * File.ftype('/tmp/socket') # => "socket" + * + * Returns 'unknown' if the type cannot be determined. */ static VALUE @@ -6162,16 +6180,39 @@ rb_stat_init_copy(VALUE copy, VALUE orig) /* * call-seq: - * stat.ftype -> string + * stat.ftype -> string + * + * Returns the string type of the object at +path+, one of: + * + * - 'file'. + * - 'directory'. + * - 'characterSpecial'. + * - 'blockSpecial'. + * - 'fifo'. + * - 'link'. + * - 'socket'. + * + * Examples: + * + * File.stat('README.md').ftype # => "file" + * File.stat('lib').ftype # => "directory" + * File.stat('/dev/null').ftype # => "characterSpecial" + * File.stat('/dev/loop0').ftype # => "blockSpecial" + * + * File.mkfifo('/tmp/pipe', 0666) + * File.stat('/tmp/pipe').ftype # => "fifo" * - * Identifies the type of stat. The return string is one of: - * ``file'', ``directory'', - * ``characterSpecial'', ``blockSpecial'', - * ``fifo'', ``link'', - * ``socket'', or ``unknown''. + * # Follows symbolic link. + * File.symlink('lib', 'lib_link') + * File.stat('lib_link').ftype # => "directory" + * # Does not follow symbolic link. + * File.lstat('lib_link').ftype # => "link" * - * File.stat("/dev/tty").ftype #=> "characterSpecial" + * require 'socket' + * UNIXServer.new('/tmp/socket') + * File.stat('/tmp/socket').ftype # => "socket" * + * Returns 'unknown' if the type cannot be determined. */ static VALUE diff --git a/kernel.rb b/kernel.rb index dc5cea1515df9a..2d1c2b0f91dddd 100644 --- a/kernel.rb +++ b/kernel.rb @@ -52,18 +52,8 @@ def clone(freeze: nil) # call-seq: # obj.frozen? -> true or false # - # Returns the freeze status of obj. - # - # a = [ "a", "b", "c" ] - # a.freeze #=> ["a", "b", "c"] - # a.frozen? #=> true - #-- - # Determines if the object is frozen. Equivalent to `Object#frozen?` in Ruby. - # @param[in] obj the object to be determines - # @retval Qtrue if frozen - # @retval Qfalse if not frozen - #++ - # + # Returns whether +self+ is frozen; + # see {Frozen Objects}[rdoc-ref:frozen_objects.md]. def frozen? Primitive.attr! :leaf Primitive.cexpr! 'rb_obj_frozen_p(self)' diff --git a/object.c b/object.c index 2e962b1c3ce107..cbafd558f9fc9d 100644 --- a/object.c +++ b/object.c @@ -1350,26 +1350,10 @@ rb_obj_dummy1(VALUE _x, VALUE _y) /* * call-seq: - * obj.freeze -> obj + * obj.freeze -> self * - * Prevents further modifications to obj. A - * FrozenError will be raised if modification is attempted. - * There is no way to unfreeze a frozen object. See also - * Object#frozen?. - * - * This method returns self. - * - * a = [ "a", "b", "c" ] - * a.freeze - * a << "z" - * - * produces: - * - * prog.rb:3:in `<<': can't modify frozen Array (FrozenError) - * from prog.rb:3 - * - * Objects of the following classes are always frozen: Integer, - * Float, Symbol. + * Freezes +self+, preventing further modifications; + * see {Frozen Objects}[rdoc-ref:frozen_objects.md]. */ VALUE diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 11ade220f0958f..d282936cc01064 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -246,9 +246,11 @@ def initialize(path) raise e.class, "Pathname.new requires a String, #to_path or #to_str", cause: nil end + # call-seq: + # pathname.freeze -> self # - # Freze self. - # + # Freezes +self+, preventing further modifications; + # see {Frozen Objects}[rdoc-ref:frozen_objects.md]. def freeze super @path.freeze @@ -1343,8 +1345,27 @@ def fnmatch(pattern, ...) File.fnmatch(pattern, @path, ...) end # See File.fnmatch? (same as #fnmatch). def fnmatch?(pattern, ...) File.fnmatch?(pattern, @path, ...) end - # See File.ftype. Returns "type" of file ("file", "directory", - # etc). + # call-seq: + # pathname.ftype -> string + # + # Returns the string type of the object at the path in +self+: + # + # Pathname('README.md').ftype # => "file" + # Pathname('lib').ftype # => "directory" + # Pathname('/dev/null').ftype # => "characterSpecial" + # Pathname('/dev/loop0').ftype # => "blockSpecial" + # + # File.mkfifo('/tmp/pipe', 0666) + # Pathname('/tmp/pipe').ftype # => "fifo" + # + # File.symlink('lib', 'lib_link') + # Pathname('lib_link').ftype # => "link" + # + # require 'socket' + # UNIXServer.new('/tmp/socket') + # Pathname('/tmp/socket').ftype # => "socket" + # + # Returns 'unknown' if the type cannot be determined. def ftype() File.ftype(@path) end # See File.link. Creates a hard link. diff --git a/test/psych/test_array.rb b/test/psych/test_array.rb index 0dc82439d44c90..064ace46593b24 100644 --- a/test/psych/test_array.rb +++ b/test/psych/test_array.rb @@ -52,6 +52,18 @@ def test_backwards_with_syck assert_equal X, x.class end + class NotAnArray + end + + def test_seq_tag_rejects_non_array_class + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !seq:#{NotAnArray} []\n" + end + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !ruby/array:#{NotAnArray} []\n" + end + end + def test_self_referential @list << @list assert_cycle(@list) diff --git a/test/psych/test_hash.rb b/test/psych/test_hash.rb index 31eba8580bb96f..7f46c551f479e0 100644 --- a/test/psych/test_hash.rb +++ b/test/psych/test_hash.rb @@ -92,6 +92,31 @@ def test_map assert_equal X, x.class end + class NotAHash + def init_with(coder) + @string = coder.map["string"].to_s + end + end + + def test_hash_with_ivars_rejects_non_hash_class + assert_raise(ArgumentError) do + Psych.unsafe_load <<~eoyml + --- !ruby/hash-with-ivars:#{NotAHash} + ivars: + '@string': ["a surprise array!"] + eoyml + end + end + + def test_hash_tag_rejects_non_hash_class + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !ruby/hash:#{NotAHash} {}\n" + end + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !map:#{NotAHash} {}\n" + end + end + def test_self_referential @hash['self'] = @hash assert_cycle(@hash) diff --git a/test/psych/test_safe_load.rb b/test/psych/test_safe_load.rb index e6ca1e142b6849..ac62df3be1533f 100644 --- a/test/psych/test_safe_load.rb +++ b/test/psych/test_safe_load.rb @@ -74,6 +74,19 @@ def test_symbol assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', permitted_classes: [Symbol]) end + def test_encoding + yaml = "--- !ruby/encoding UTF-8\n" + assert_raise(Psych::DisallowedClass) do + Psych.safe_load yaml + end + assert_raise(Psych::DisallowedClass) do + Psych.safe_load yaml, permitted_classes: [] + end + + assert_equal Encoding::UTF_8, Psych.safe_load(yaml, permitted_classes: [Encoding]) + assert_equal Encoding::UTF_8, Psych.safe_load(yaml, permitted_classes: %w{ Encoding }) + end + def test_foo assert_raise(Psych::DisallowedClass) do Psych.safe_load '--- !ruby/object:Foo {}', permitted_classes: [Foo] diff --git a/test/psych/test_string.rb b/test/psych/test_string.rb index cfd235a5194b97..73fb3933ccea25 100644 --- a/test/psych/test_string.rb +++ b/test/psych/test_string.rb @@ -182,6 +182,18 @@ def test_subclass_with_attributes assert_equal 1, y.val end + class NotAString + end + + def test_string_tag_rejects_non_string_class + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !ruby/string:#{NotAString} foo\n" + end + assert_raise(ArgumentError) do + Psych.unsafe_load "--- !ruby/string:#{NotAString}\nstr: foo\n" + end + end + def test_string_with_base_60 yaml = Psych.dump '01:03:05' assert_match "'01:03:05'", yaml diff --git a/vm_dump.c b/vm_dump.c index 00b03ff58d117b..6f1e179873d99d 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -1530,7 +1530,7 @@ rb_vm_bugreport(const void *ctx, FILE *errout) } { -#ifndef RUBY_ASAN_ENABLED +#if !defined(RUBY_ASAN_ENABLED) && !USE_MODULAR_GC # ifdef PROC_MAPS_NAME { FILE *fp = fopen(PROC_MAPS_NAME, "r");