diff --git a/.github/workflows/check_sast.yml b/.github/workflows/check_sast.yml index a33b242892cd21..34cf7e4b9b7a44 100644 --- a/.github/workflows/check_sast.yml +++ b/.github/workflows/check_sast.yml @@ -78,14 +78,14 @@ jobs: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: languages: ${{ matrix.language }} build-mode: none config-file: .github/codeql/codeql-config.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: category: '/language:${{ matrix.language }}' upload: False @@ -127,7 +127,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index b89841ae1ca314..7877ff7cee3769 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -62,7 +62,8 @@ jobs: with: name: compilation-source path: ${{ runner.temp }}/compilation-source.tar - compression-level: 0 + compression-level: 5 + overwrite: true retention-days: 1 compile: @@ -183,7 +184,6 @@ jobs: - { uses: *compilers, name: 'NDEBUG', with: { cppflags: '-DNDEBUG' }, timeout-minutes: 8, if: "${{ matrix.shard == '15' }}" } - { uses: *compilers, name: 'RUBY_DEBUG', with: { cppflags: '-DRUBY_DEBUG' }, timeout-minutes: 8, if: "${{ matrix.shard == '15' }}" } - - { uses: *compilers, name: 'ARRAY_DEBUG', with: { cppflags: '-DARRAY_DEBUG' }, timeout-minutes: 8, if: "${{ matrix.shard == '15' }}" } - { uses: *compilers, name: 'CCAN_LIST_DEBUG', with: { cppflags: '-DCCAN_LIST_DEBUG' }, timeout-minutes: 8, if: "${{ matrix.shard == '15' }}" } - { uses: *compilers, name: 'CPDEBUG=-1', with: { cppflags: '-DCPDEBUG=-1' }, timeout-minutes: 8, if: "${{ matrix.shard == '04' }}" } - { uses: *compilers, name: 'ENC_DEBUG', with: { cppflags: '-DENC_DEBUG' }, timeout-minutes: 8, if: "${{ matrix.shard == '04' }}" } @@ -191,10 +191,8 @@ jobs: - { uses: *compilers, name: 'VM_DEBUG_VERIFY_METHOD_CACHE', with: { cppflags: '-DVM_DEBUG_VERIFY_METHOD_CACHE' }, timeout-minutes: 8, if: "${{ matrix.shard == '04' }}" } - { uses: *compilers, name: 'ID_TABLE_DEBUG', with: { cppflags: '-DID_TABLE_DEBUG' }, timeout-minutes: 8, if: "${{ matrix.shard == '17' }}" } - - { uses: *compilers, name: 'RGENGC_DEBUG=-1', with: { cppflags: '-DRGENGC_DEBUG=-1' }, timeout-minutes: 8, if: "${{ matrix.shard == '17' }}" } - { uses: *compilers, name: 'SYMBOL_DEBUG', with: { cppflags: '-DSYMBOL_DEBUG' }, timeout-minutes: 8, if: "${{ matrix.shard == '17' }}" } - { uses: *compilers, name: 'RGENGC_CHECK_MODE', with: { cppflags: '-DRGENGC_CHECK_MODE' }, timeout-minutes: 8, if: "${{ matrix.shard == '06' }}" } - - { uses: *compilers, name: 'VM_CHECK_MODE', with: { cppflags: '-DVM_CHECK_MODE' }, timeout-minutes: 8, if: "${{ matrix.shard == '06' }}" } - { uses: *compilers, name: 'USE_EMBED_CI=0', with: { cppflags: '-DUSE_EMBED_CI=0' }, timeout-minutes: 8, if: "${{ matrix.shard == '06' }}" } - { uses: *compilers, name: 'USE_FLONUM=0', with: { cppflags: '-DUSE_FLONUM=0', append_configure: '--disable-yjit --disable-zjit' }, timeout-minutes: 8, if: "${{ matrix.shard == '06' }}" } @@ -206,7 +204,6 @@ jobs: - { uses: *compilers, name: 'DEBUG_INTEGER_PACK', with: { cppflags: '-DDEBUG_INTEGER_PACK' }, timeout-minutes: 8, if: "${{ matrix.shard == '16' }}" } - { uses: *compilers, name: 'RGENGC_PROFILE', with: { cppflags: '-DRGENGC_PROFILE' }, timeout-minutes: 8, if: "${{ matrix.shard == '16' }}" } - - { uses: *compilers, name: 'GC_DEBUG_STRESS_TO_CLASS', with: { cppflags: '-DGC_DEBUG_STRESS_TO_CLASS' }, timeout-minutes: 8, if: "${{ matrix.shard == '21' }}" } - { uses: *compilers, name: 'GC_DEBUG', with: { cppflags: '-DGC_DEBUG' }, timeout-minutes: 8, if: "${{ matrix.shard == '21' }}" } - { uses: *compilers, name: 'GC_ENABLE_LAZY_SWEEP=0', with: { cppflags: '-DGC_ENABLE_LAZY_SWEEP=0' }, timeout-minutes: 8, if: "${{ matrix.shard == '21' }}" } - { uses: *compilers, name: 'GC_PROFILE_DETAIL_MEMORY', with: { cppflags: '-DGC_PROFILE_DETAIL_MEMORY' }, timeout-minutes: 8, if: "${{ matrix.shard == '21' }}" } @@ -245,7 +242,7 @@ jobs: - name: Create empty source artifact run: touch compilation-source.empty working-directory: ${{ runner.temp }} - if: ${{ always() }} + if: ${{ needs.compile.result == 'success' }} - name: Shrink source artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -253,7 +250,7 @@ jobs: path: ${{ runner.temp }}/compilation-source.empty overwrite: true retention-days: 1 - if: ${{ always() }} + if: ${{ needs.compile.result == 'success' }} - run: false working-directory: diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 541d2a869b18f8..6a2f162b3a9c56 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -73,6 +73,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: sarif_file: results.sarif diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 4d3ffcb9ea7bf0..1332e25a2c7e1f 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -98,7 +98,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@5b4d68e2e660441203ab128a23676f1e4faf1532 # v2.86.3 + - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 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 63836e3a45305c..66f9771e76117b 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -152,7 +152,7 @@ jobs: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@5b4d68e2e660441203ab128a23676f1e4faf1532 # v2.86.3 + - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/file.c b/file.c index 41275eeebc0c84..d173cfe1858890 100644 --- a/file.c +++ b/file.c @@ -1794,19 +1794,27 @@ rb_access(VALUE fname, int mode) /* * call-seq: - * File.directory?(path) -> true or false + * File.directory?(object) -> true or false * - * With string +object+ given, returns +true+ if +path+ is a string path - * leading to a directory, or to a symbolic link to a directory; +false+ otherwise: + * Returns whether the given +object+ represents a directory; + * +object+ may be a string path or an IO object: * - * File.directory?('.') # => true - * File.directory?('foo') # => false - * File.symlink('.', 'dirlink') # => 0 - * File.directory?('dirlink') # => true - * File.symlink('t,txt', 'filelink') # => 0 - * File.directory?('filelink') # => false + * File.directory?('/etc') # => true + * File.directory?('lib') # => true + * File.directory?('README.md') # => false + * File.directory?('nosuch') # => false + * File.directory?($stdin) # => false * - * Argument +path+ can be an IO object. + * Follows symbolic links: + * + * dirpath = 'doc/dirname' + * File.symlink('.', dirpath) + * File.directory?(dirpath) # => true + * File.unlink(dirpath) + * filepath = 't.tmp' + * File.symlink('README.md', filepath) + * File.directory?(filepath) # => false + * File.unlink(filepath) * */ @@ -2691,17 +2699,36 @@ rb_file_mtime(VALUE obj) /* * call-seq: - * File.ctime(file_name) -> time + * File.ctime(object) -> time * - * Returns the change time for the named file (the time at which - * directory information about the file was changed, not the file - * itself). + * Returns a Time object, based on the given +object+, + * which is a string path or an IO object. * - * _file_name_ can be an IO object. + * On Windows, returns the #birthtime for +object+. * - * Note that on Windows (NTFS), returns creation time (birth time). + * On other systems, + * returns a new Time object containing the time of the most recent + * metadata change to the entry represented by +object+; + * see {File System Timestamps}[rdoc-ref:file/timestamps.md]: * - * File.ctime("testfile") #=> Wed Apr 09 08:53:13 CDT 2003 + * # Create directory; directory ctime established. + * dirpath = 'doc/foo' + * Dir.mkdir(dirpath) + * File.ctime(dirpath) # => 2026-08-23 10:43:05.473815913 -0500 + * # Create file therein; file ctime established; directory ctime updated. + * filepath = File.join(dirpath, 't.tmp') # => "doc/foo/t.tmp" + * File.write(filepath, 'foo') + * File.ctime(filepath) # => 2026-08-23 10:43:37.560429379 -0500 + * File.ctime(dirpath) # => 2026-08-23 10:43:37.560429379 -0500 + * # Write file; file ctime updated; directory ctime not updated. + * File.write(filepath, 'bar') + * File.ctime(filepath) # => 2026-08-23 10:46:49.299180833 -0500 + * File.ctime(dirpath) # => 2026-08-23 10:43:37.560429379 -0500 + * # Read file; neither ctime updated. + * File.read(filepath) + * File.ctime(filepath) # => 2026-08-23 10:46:49.299180833 -0500 + * File.ctime(dirpath) # => 2026-08-23 10:43:37.560429379 -0500 + * FileUtils.rm_rf(dirpath) # Clean up. * */ @@ -3752,18 +3779,18 @@ unlink_internal(const char *path, void *arg) /* * call-seq: - * File.delete(file_name, ...) -> integer - * File.unlink(file_name, ...) -> integer - * - * Deletes the named files, returning the number of names - * passed as arguments. Raises an exception on any error. - * Since the underlying implementation relies on the - * unlink(2) system call, the type of - * exception raised depends on its error type (see - * https://man7.org/linux/man-pages/man2/unlink.2.html) and has the form of - * e.g. Errno::ENOENT. - * - * See also Dir::rmdir. + * File.delete(*filepaths) -> integer + * File.unlink(*filepaths) -> integer + * + * Removes the file entry at each path in +filepaths+; + * returns the number of removed files. + * + * File.write('t.tmp', 'foo') + * File.write('u.tmp', 'bar') + * File.delete('t.tmp', 'u.tmp') # => 2 + * + * Raises an exception on any error; + * some files may have been deleted before the path causing the error. */ static VALUE diff --git a/io_buffer.c b/io_buffer.c index f9fa1cf23d5e16..e512357db7d7fc 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1007,17 +1007,19 @@ io_buffer_validate_slice(VALUE source, void *base, size_t size) rb_io_buffer_get_bytes(source, &source_base, &source_size); } - // Source is invalid: - if (source_base == NULL) return 0; + uintptr_t source_address = (uintptr_t)source_base; + uintptr_t address = (uintptr_t)base; // Base is out of range: - if (base < source_base) return 0; + if (address < source_address) return 0; - const void *source_end = (char*)source_base + source_size; - const void *end = (char*)base + size; + uintptr_t offset = address - source_address; - // End is out of range: - if (end > source_end) return 0; + // Base is beyond the end of the source: + if (offset > source_size) return 0; + + // End is beyond the end of the source: + if (size > source_size - (size_t)offset) return 0; // It seems okay: return 1; @@ -1316,10 +1318,17 @@ rb_io_buffer_size(VALUE self) /* * call-seq: valid? -> true or false * - * Returns whether the buffer buffer is accessible. + * Returns whether the buffer's recorded memory range currently exists within + * its source. A buffer which is not a slice is always valid, including a null + * buffer. + * + * A slice can become invalid if its source is freed, transferred, shrunk past + * the slice, or reallocated at a different address. Validity is dynamic: if + * the source later contains the same address range again, the slice becomes + * valid again. * - * A buffer becomes invalid if it is a slice of another buffer (or string) - * which has been freed or re-allocated at a different address. + * #valid?, #null? and #empty? describe independent properties. For example, + * an invalid slice can still have a non-null address and a non-zero size. */ static VALUE rb_io_buffer_valid_p(VALUE self) @@ -1332,8 +1341,11 @@ rb_io_buffer_valid_p(VALUE self) /* * call-seq: null? -> true or false * - * If the buffer was freed with #free, transferred with #transfer, or was - * never allocated in the first place. + * Returns whether the buffer has no recorded base address. + * + * A buffer is null if it was freed with #free, transferred with #transfer, or + * was never allocated in the first place. A zero-sized buffer or slice may + * have a non-null address, so #null? and #empty? are distinct properties. * * buffer = IO::Buffer.new(0) * buffer.null? #=> true @@ -1354,9 +1366,11 @@ rb_io_buffer_null_p(VALUE self) /* * call-seq: empty? -> true or false * - * If the buffer has 0 size: it is created by ::new with size 0, or with ::for - * from an empty string. (Note that empty files can't be mapped, so the buffer - * created with ::map will never be empty.) + * Returns whether the buffer has zero size. + * + * A buffer can be empty but have a non-null address, for example a zero-sized + * slice or a buffer created with ::for from an empty string. Therefore + * #empty? does not imply #null?. */ static VALUE rb_io_buffer_empty_p(VALUE self) @@ -1776,7 +1790,7 @@ rb_io_buffer_slice(struct rb_io_buffer *buffer, VALUE self, size_t offset, size_ struct rb_io_buffer *slice = get_io_buffer(instance); slice->flags |= (buffer->flags & RB_IO_BUFFER_READONLY); - slice->base = (char*)buffer->base + offset; + slice->base = buffer->base ? (char*)buffer->base + offset : NULL; slice->size = length; // Slices retain their root buffer. If this buffer is already a slice, @@ -3002,7 +3016,9 @@ io_buffer_get_string(int argc, VALUE *argv, VALUE self) io_buffer_validate_range(buffer, offset, length); - return rb_enc_str_new((const char*)base + offset, length, encoding); + const char *data = base ? (const char*)base + offset : NULL; + + return rb_enc_str_new(data, length, encoding); } /* diff --git a/lib/bundler/fetcher/connection_pools.rb b/lib/bundler/fetcher/connection_pools.rb index a2e72af74b8020..09514b610cb388 100644 --- a/lib/bundler/fetcher/connection_pools.rb +++ b/lib/bundler/fetcher/connection_pools.rb @@ -72,6 +72,9 @@ def proxy_for(uri) env_proxy_for(uri) end return unless proxy + # NO_PROXY="*" bypasses the proxy for every host, which + # Gem::URI::Generic.use_proxy? does not understand. + return if no_proxy_env.strip == "*" return unless Gem::URI::Generic.use_proxy?(uri.hostname, nil, uri.port, no_proxy_env) proxy end @@ -90,6 +93,10 @@ def build_connection(uri, proxy_uri) # :nodoc: configure_ssl(connection) if uri.scheme == "https" connection.open_timeout = @timeout connection.read_timeout = @timeout + # Stale connections are already resent by Gem::Request#perform_request, + # and Bundler::Retry retries whole requests, so the Gem::Net::HTTP + # level retry would only multiply the time spent on a timing out host. + connection.max_retries = 0 connection.start connection end diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 8cf56c9a130629..88f7f67b4bcc4c 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -179,7 +179,7 @@ Cooldown filtering depends on the gem server providing a per\-version \fBcreated .IP "\(bu" 4 \fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. .IP "\(bu" 4 -\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. +\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests, applied to each request individually\. A network operation retried by Bundler may wait a multiple of this value in total\. Defaults to \fB10\fR\. .IP "\(bu" 4 \fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. .IP "\(bu" 4 @@ -330,6 +330,12 @@ Any \fB\.\fR characters in a host name are mapped to a double underscore (\fB__\ .IP "" 0 .P This means that if you have a gem server named \fBmy\.gem\-host\.com\fR, you'll need to use the \fBBUNDLE_MY__GEM___HOST__COM\fR variable to configure credentials for it through ENV\. +.SH "PROXY SUPPORT" +Bundler reads the proxy for a gem source from the \fB:http_proxy\fR key in the RubyGems configuration file (\fB~/\.gemrc\fR), falling back to the environment\. For an \fBhttps\fR source, \fBhttps_proxy\fR (or \fBHTTPS_PROXY\fR) takes precedence over \fBhttp_proxy\fR (or \fBHTTP_PROXY\fR), which is only used when no https\-specific proxy is set\. Proxy credentials that are not embedded in the proxy URL are read from \fBHTTP_PROXY_USER\fR and \fBHTTP_PROXY_PASS\fR, or from \fBHTTPS_PROXY_USER\fR and \fBHTTPS_PROXY_PASS\fR when the https proxy is used\. +.P +Setting \fB:http_proxy: :no_proxy\fR in the RubyGems configuration file disables proxies entirely, including any proxy environment variables\. To keep using a proxy from the environment, remove that line\. To configure a proxy in the file, replace \fB:no_proxy\fR with an explicit proxy URL\. +.P +The \fBNO_PROXY\fR (or \fBno_proxy\fR) environment variable lists hosts that are reached directly, bypassing the proxy\. An entry matches the host itself and its subdomains, so \fBNO_PROXY=example\.com\fR bypasses both \fBexample\.com\fR and \fBsub\.example\.com\fR, but not \fBmyexample\.com\fR\. An entry starting with a dot matches only subdomains\. A value of \fB*\fR bypasses the proxy for every host\. .SH "CONFIGURE BUNDLER DIRECTORIES" Bundler's home, cache and plugin directories and config file can be configured through environment variables\. The default location for Bundler's home directory is \fB~/\.bundle\fR, which all directories inherit from by default\. The following outlines the available environment variables and their default values .IP "" 4 diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 27fd5e040b5abb..6e9fedd109afb9 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -293,7 +293,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `system_bindir` (`BUNDLE_SYSTEM_BINDIR`): The location where RubyGems installs binstubs. Defaults to `Gem.bindir`. * `timeout` (`BUNDLE_TIMEOUT`): - The seconds allowed before timing out for network requests. Defaults to `10`. + The seconds allowed before timing out for network requests, applied to each + request individually. A network operation retried by Bundler may wait a + multiple of this value in total. Defaults to `10`. * `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`): Require passing `--all` to `bundle update` when everything should be updated, and disallow passing no options to `bundle update`. @@ -452,6 +454,27 @@ This means that if you have a gem server named `my.gem-host.com`, you'll need to use the `BUNDLE_MY__GEM___HOST__COM` variable to configure credentials for it through ENV. +## PROXY SUPPORT + +Bundler reads the proxy for a gem source from the `:http_proxy` key in the +RubyGems configuration file (`~/.gemrc`), falling back to the environment. +For an `https` source, `https_proxy` (or `HTTPS_PROXY`) takes precedence over +`http_proxy` (or `HTTP_PROXY`), which is only used when no https-specific +proxy is set. Proxy credentials that are not embedded in the proxy URL are +read from `HTTP_PROXY_USER` and `HTTP_PROXY_PASS`, or from `HTTPS_PROXY_USER` +and `HTTPS_PROXY_PASS` when the https proxy is used. + +Setting `:http_proxy: :no_proxy` in the RubyGems configuration file disables +proxies entirely, including any proxy environment variables. To keep using a +proxy from the environment, remove that line. To configure a proxy in the +file, replace `:no_proxy` with an explicit proxy URL. + +The `NO_PROXY` (or `no_proxy`) environment variable lists hosts that are +reached directly, bypassing the proxy. An entry matches the host itself and +its subdomains, so `NO_PROXY=example.com` bypasses both `example.com` and +`sub.example.com`, but not `myexample.com`. An entry starting with a dot +matches only subdomains. A value of `*` bypasses the proxy for every host. + ## CONFIGURE BUNDLER DIRECTORIES Bundler's home, cache and plugin directories and config file can be configured diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb index b353642b4062fe..6de398a129b26f 100644 --- a/lib/bundler/stub_specification.rb +++ b/lib/bundler/stub_specification.rb @@ -126,7 +126,9 @@ def inspect def _remote_specification @_remote_specification ||= begin rs = stub.to_spec - if rs.equal?(self) # happens when to_spec gets the spec from Gem.loaded_specs + # Gem::StubSpecification#to_spec may return an activated specification + # with the same name and version from a different gem installation. + if rs.equal?(self) || rs.loaded_from != loaded_from rs = Gem::Specification.load(loaded_from) Bundler.rubygems.stub_set_spec(stub, rs) end diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index 01e7e0629a903f..b2f952dd7ad9b4 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -50,6 +50,9 @@ def https?(uri) end def no_proxy?(host, env_no_proxy) + # A lone "*" entry bypasses the proxy for every host + return true if env_no_proxy.include?("*") + host = host.downcase env_no_proxy.any? do |pattern| diff --git a/pathname_builtin.rb b/pathname_builtin.rb index d2300ab56bebb3..1471350488409d 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2351,6 +2351,22 @@ def grpowned?() FileTest.grpowned?(@path) end # Pathname('lib').directory? # => true # Pathname('README.md').directory? # => false # Pathname('nosuch').directory? # => false + # Pathname($stdin).directory? # => false + # ``` + # + # Follows symbolic links: + # + # ```ruby + # target_pn = Pathname('doc') + # link_pn = Pathname('link') + # link_pn.make_symlink(target_pn) + # link_pn.directory? # => true + # link_pn.delete + # target_pn = Pathname('README.md') + # link_pn = Pathname('link') + # link_pn.make_symlink(target_pn) + # link_pn.directory? # => false + # link_pn.delete # ``` # def directory?() FileTest.directory?(@path) end diff --git a/spec/bundler/bundler/fetcher_spec.rb b/spec/bundler/bundler/fetcher_spec.rb index 87a49ea6fc7865..8bac5548b9241c 100644 --- a/spec/bundler/bundler/fetcher_spec.rb +++ b/spec/bundler/bundler/fetcher_spec.rb @@ -82,6 +82,11 @@ expect(fetcher.http_proxy).to be_nil end end + it "bypass proxy for every host when no_proxy is '*'" do + with_env_vars("HTTP_PROXY" => "http://proxy-example5.com", "NO_PROXY" => "*") do + expect(fetcher.http_proxy).to be_nil + end + end end def configured_connection diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 1e0e3a7c244b42..a0b56a3d2970a4 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -2062,6 +2062,37 @@ def run expect(Dir.glob(vendored_gems("bin/*"))).to eq(expected_executables) end + it "preserves bundled native extensions when BUNDLE_CLEAN removes another gem" do + build_repo4 do + build_gem "native_child", "1.0", &:add_c_extension + build_gem "native_parent", "1.0" do |s| + s.add_dependency "native_child", "1.0" + end + build_gem "cleanup_target", "1.0" + build_gem "cleanup_target", "2.0" + end + + system_gems %w[native_child-1.0 native_parent-1.0 cleanup_target-1.0], gem_repo: gem_repo4 + + install_gemfile <<~G, env: { "BUNDLE_CLEAN" => "false", "BUNDLE_PATH" => "vendor/bundle" } + source "https://gem.repo4" + gem "native_parent" + gem "cleanup_target", "1.0" + G + + extension_dir = Pathname.glob("#{vendored_gems}/extensions/*/*/native_child-1.0").first + expect(extension_dir).to exist + + install_gemfile <<~G, env: { "BUNDLE_CLEAN" => "true", "BUNDLE_PATH" => "vendor/bundle", "RUBYOPT" => "-rnative_child" } + source "https://gem.repo4" + gem "native_parent" + gem "cleanup_target", "2.0" + G + + expect(out).to include("Removing cleanup_target (1.0)") + expect(extension_dir).to exist + end + it "preserves lockfile versions conservatively" do build_repo4 do build_gem "mypsych", "4.0.6" do |s| diff --git a/spec/ruby/core/io/buffer/empty_spec.rb b/spec/ruby/core/io/buffer/empty_spec.rb index 4cceb4dc0db765..b439bf7ed49a1d 100644 --- a/spec/ruby/core/io/buffer/empty_spec.rb +++ b/spec/ruby/core/io/buffer/empty_spec.rb @@ -24,4 +24,13 @@ @buffer = IO::Buffer.new(4) @buffer.slice(3, 0).empty?.should == true end + + it "is false for an invalid slice with a non-zero size" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.free + + slice.valid?.should == false + slice.empty?.should == false + end end diff --git a/spec/ruby/core/io/buffer/null_spec.rb b/spec/ruby/core/io/buffer/null_spec.rb index 380a98bde1d41b..83837014922b1c 100644 --- a/spec/ruby/core/io/buffer/null_spec.rb +++ b/spec/ruby/core/io/buffer/null_spec.rb @@ -24,4 +24,13 @@ @buffer = IO::Buffer.new(4) @buffer.slice(3, 0).null?.should == false end + + it "is false for an invalid slice with a recorded address" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.free + + slice.valid?.should == false + slice.null?.should == false + end end diff --git a/spec/ruby/core/io/buffer/valid_spec.rb b/spec/ruby/core/io/buffer/valid_spec.rb index e4be10645b6c0b..4bab75653053db 100644 --- a/spec/ruby/core/io/buffer/valid_spec.rb +++ b/spec/ruby/core/io/buffer/valid_spec.rb @@ -50,6 +50,39 @@ slice.valid?.should == true end + ruby_version_is "4.1" do + it "is true for an empty slice of an empty buffer" do + @buffer = IO::Buffer.new(0) + slice = @buffer.slice(0, 0) + + slice.valid?.should == true + slice.get_string.should == "" + end + + it "tracks whether its empty range exists in the source" do + @buffer = IO::Buffer.new(0) + slice = @buffer.slice(0, 0) + + slice.valid?.should == true + + @buffer.resize(1) + slice.valid?.should == false + -> { slice.get_string }.should.raise(IO::Buffer::InvalidatedError) + + @buffer.resize(0) + slice.valid?.should == true + slice.get_string.should == "" + end + + it "is false when its empty range no longer belongs to the source" do + @buffer = IO::Buffer.new(1) + slice = @buffer.slice(1, 0) + + @buffer.resize(0) + slice.valid?.should == false + end + end + context "when buffer is resized" do it "is false when slice becomes outside the buffer" do @buffer = IO::Buffer.new(4) @@ -73,6 +106,25 @@ slice.valid?.should == false end + it "is independent of null? and empty?" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.free + + slice.valid?.should == false + slice.null?.should == false + slice.empty?.should == false + end + + it "can be true for a non-null empty slice" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(2, 0) + + slice.valid?.should == true + slice.null?.should == false + slice.empty?.should == true + end + it "is false for a slice of a freed file-backed buffer" do File.open(__FILE__, "r") do |file| @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) diff --git a/test/rubygems/test_gem_request_connection_pools.rb b/test/rubygems/test_gem_request_connection_pools.rb index 2860deabf7132d..3ab897048daa50 100644 --- a/test/rubygems/test_gem_request_connection_pools.rb +++ b/test/rubygems/test_gem_request_connection_pools.rb @@ -96,6 +96,14 @@ def test_to_proxy_eh_wildcard refute no_proxy, "wildcard mismatch" end + def test_to_proxy_star + pools = Gem::Request::ConnectionPools.new nil, [] + + no_proxy = pools.send :no_proxy?, "rubygems.example", ["*"] + + assert no_proxy, "asterisk matches every host" + end + def test_net_http_args pools = Gem::Request::ConnectionPools.new nil, [] @@ -133,6 +141,19 @@ def test_net_http_args_no_proxy ENV["no_proxy"] = orig_no_proxy end + def test_net_http_args_no_proxy_star + orig_no_proxy = ENV["no_proxy"] + ENV["no_proxy"] = "*" + + pools = Gem::Request::ConnectionPools.new nil, [] + + net_http_args = pools.send :net_http_args, Gem::URI("http://example"), @proxy + + assert_equal ["example", 80, nil, nil], net_http_args + ensure + ENV["no_proxy"] = orig_no_proxy + end + def test_thread_waits_for_connection uri = Gem::URI.parse("http://example/some_endpoint") pools = Gem::Request::ConnectionPools.new nil, [] diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 15e21792c36a60..bc8b327e35b002 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2265,9 +2265,11 @@ vm_search_method_slowpath0(VALUE cd_owner, struct rb_call_data *cd, VALUE klass) { #if USE_DEBUG_COUNTER const struct rb_callcache *old_cc = cd->cc; + const rb_callable_method_entry_t *const old_cme = old_cc ? old_cc->cme_ : NULL; #endif const struct rb_callcache *cc = rb_vm_search_method_slowpath(cd->ci, klass); + const rb_callable_method_entry_t *const new_cme = vm_cc_cme(cc); #if OPT_INLINE_METHOD_CACHE cd->cc = cc; @@ -2289,11 +2291,10 @@ vm_search_method_slowpath0(VALUE cd_owner, struct rb_call_data *cd, VALUE klass) else if (old_cc == cc) { RB_DEBUG_COUNTER_INC(mc_inline_miss_same_cc); } - else if (vm_cc_cme(old_cc) == vm_cc_cme(cc)) { + else if (old_cme == new_cme) { RB_DEBUG_COUNTER_INC(mc_inline_miss_same_cme); } - else if (vm_cc_cme(old_cc) && vm_cc_cme(cc) && - vm_cc_cme(old_cc)->def == vm_cc_cme(cc)->def) { + else if (old_cme && new_cme && old_cme->def == new_cme->def) { RB_DEBUG_COUNTER_INC(mc_inline_miss_same_def); } else { @@ -2302,8 +2303,7 @@ vm_search_method_slowpath0(VALUE cd_owner, struct rb_call_data *cd, VALUE klass) #endif #endif // OPT_INLINE_METHOD_CACHE - VM_ASSERT(vm_cc_cme(cc) == NULL || - vm_cc_cme(cc)->called_id == vm_ci_mid(cd->ci)); + if (new_cme) VM_ASSERT(new_cme->called_id == vm_ci_mid(cd->ci)); return cc; }