Environment
- JRuby 10.0.2.0 (Ruby 3.4.2 compat)
- macOS / aarch64
Summary
Thread::Backtrace::Location#absolute_path returns a relative path (identical to #path) when the source file was loaded via Kernel#load with a bare relative path that is resolved via cwd (e.g. load "file.rb"). On MRI, absolute_path correctly resolves to the full absolute path.
The bug is specific to the case where load resolves the file via the current working directory. It does not occur when:
load is given a ./ prefix (e.g. load "./file.rb")
load is given an absolute path
load resolves the file via $LOAD_PATH
require is used (always resolves via $LOAD_PATH)
require_relative is used
Reproduction Script
#!/usr/bin/env ruby
# frozen_string_literal: true
# Reproduction: Thread::Backtrace::Location#absolute_path returns a relative path
# when the source file was loaded via `load` with a bare relative path.
#
# Expected: absolute_path always returns an absolute path (or nil).
# Actual on JRuby: absolute_path returns the same relative string as #path.
require "tmpdir"
class Capturer
attr_reader :location
def capture
@location = caller_locations(1, 1).to_a.first
end
end
capturer = Capturer.new
dir = Dir.mktmpdir
begin
Dir.chdir(dir) do
File.write("test_file.rb", <<~'RUBY')
$capturer.capture
RUBY
$capturer = capturer
puts "RUBY_ENGINE: #{RUBY_ENGINE}"
puts "RUBY_VERSION: #{RUBY_VERSION}"
puts "JRUBY_VERSION: #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : 'N/A'}"
puts
# Helper to check result
check = ->(loc) do
abs = loc.absolute_path
if abs && !abs.start_with?("/")
puts " BUG: absolute_path is not absolute!"
else
puts " OK: absolute_path is absolute"
end
end
# --- load ---
# Case 1: load with bare relative path (BUG on JRuby)
load "test_file.rb"
loc = capturer.location
puts '=== load "test_file.rb" (bare relative, cwd resolution) ==='
puts " path: #{loc.path.inspect}"
puts " absolute_path: #{loc.absolute_path.inspect}"
check.call(loc)
puts
# Case 2: load with bare relative path, dir in $LOAD_PATH (OK)
$LOAD_PATH.unshift(dir)
load "test_file.rb"
loc = capturer.location
puts '=== load "test_file.rb" (bare relative, $LOAD_PATH resolution) ==='
puts " path: #{loc.path.inspect}"
puts " absolute_path: #{loc.absolute_path.inspect}"
check.call(loc)
$LOAD_PATH.delete(dir)
puts
# Case 3: load with ./ relative path (OK)
load "./test_file.rb"
loc = capturer.location
puts '=== load "./test_file.rb" (./ relative path) ==='
puts " path: #{loc.path.inspect}"
puts " absolute_path: #{loc.absolute_path.inspect}"
check.call(loc)
puts
# Case 4: load with absolute path (OK)
load File.join(dir, "test_file.rb")
loc = capturer.location
puts '=== load "/absolute/path/test_file.rb" ==='
puts " path: #{loc.path.inspect}"
puts " absolute_path: #{loc.absolute_path.inspect}"
check.call(loc)
puts
# --- require ---
# Case 5: require via $LOAD_PATH (OK)
$LOAD_PATH.unshift(dir)
$LOADED_FEATURES.delete_if { |f| f.include?("test_file") }
require "test_file"
loc = capturer.location
puts '=== require "test_file" (via $LOAD_PATH) ==='
puts " path: #{loc.path.inspect}"
puts " absolute_path: #{loc.absolute_path.inspect}"
check.call(loc)
$LOAD_PATH.delete(dir)
puts
# Case 6: require with ./ relative path (OK)
$LOADED_FEATURES.delete_if { |f| f.include?("test_file") }
require "./test_file"
loc = capturer.location
puts '=== require "./test_file" (./ relative path) ==='
puts " path: #{loc.path.inspect}"
puts " absolute_path: #{loc.absolute_path.inspect}"
check.call(loc)
puts
# Case 7: require_relative (OK)
File.write("wrapper.rb", "require_relative 'test_file'\n")
$LOADED_FEATURES.delete_if { |f| f.include?("test_file") }
load "wrapper.rb"
loc = capturer.location
puts '=== require_relative "test_file" (via wrapper) ==='
puts " path: #{loc.path.inspect}"
puts " absolute_path: #{loc.absolute_path.inspect}"
check.call(loc)
end
ensure
FileUtils.rm_rf(dir)
end
Expected Behavior
Here's what that script produces on MRI 3.4.5:
RUBY_ENGINE: ruby
RUBY_VERSION: 3.4.5
JRUBY_VERSION: N/A
=== load "test_file.rb" (bare relative, cwd resolution) ===
path: "test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
OK: absolute_path is absolute
=== load "test_file.rb" (bare relative, $LOAD_PATH resolution) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
OK: absolute_path is absolute
=== load "./test_file.rb" (./ relative path) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
OK: absolute_path is absolute
=== load "/absolute/path/test_file.rb" ===
path: "/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
OK: absolute_path is absolute
=== require "test_file" (via $LOAD_PATH) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
OK: absolute_path is absolute
=== require "./test_file" (./ relative path) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
OK: absolute_path is absolute
=== require_relative "test_file" (via wrapper) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-34796-kjeupa/test_file.rb"
OK: absolute_path is absolute
Actual Behavior
Here's the result on JRuby:
RUBY_ENGINE: jruby
RUBY_VERSION: 3.4.2
JRUBY_VERSION: 10.0.2.0
=== load "test_file.rb" (bare relative, cwd resolution) ===
path: "test_file.rb"
absolute_path: "test_file.rb"
BUG: absolute_path is not absolute!
=== load "test_file.rb" (bare relative, $LOAD_PATH resolution) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
OK: absolute_path is absolute
=== load "./test_file.rb" (./ relative path) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
OK: absolute_path is absolute
=== load "/absolute/path/test_file.rb" ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
OK: absolute_path is absolute
=== require "test_file" (via $LOAD_PATH) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
OK: absolute_path is absolute
=== require "./test_file" (./ relative path) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
OK: absolute_path is absolute
=== require_relative "test_file" (via wrapper) ===
path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
absolute_path: "/private/var/folders/t0/0v26g3fs33gc0vk7hydx3wzw0000gp/T/d20260216-33652-d19nm7/test_file.rb"
OK: absolute_path is absolute
Scope of the bug
| Method |
Affected? |
load "file.rb" (cwd resolution) |
YES |
load "file.rb" ($LOAD_PATH resolution) |
no |
load "./file.rb" |
no |
load "/absolute/path/file.rb" |
no |
require "file" (via $LOAD_PATH) |
no |
require "./file" |
no |
require_relative "file" |
no |
Impact
Code that relies on absolute_path being absolute (e.g. Pathname#relative_path_from) fails with ArgumentError: different prefix.
Environment
Summary
Thread::Backtrace::Location#absolute_pathreturns a relative path (identical to#path) when the source file was loaded viaKernel#loadwith a bare relative path that is resolved viacwd(e.g.load "file.rb"). On MRI,absolute_pathcorrectly resolves to the full absolute path.The bug is specific to the case where
loadresolves the file via the current working directory. It does not occur when:loadis given a./prefix (e.g.load "./file.rb")loadis given an absolute pathloadresolves the file via$LOAD_PATHrequireis used (always resolves via$LOAD_PATH)require_relativeis usedReproduction Script
Expected Behavior
Here's what that script produces on MRI 3.4.5:
Actual Behavior
Here's the result on JRuby:
Scope of the bug
load "file.rb"(cwd resolution)load "file.rb"($LOAD_PATH resolution)load "./file.rb"load "/absolute/path/file.rb"require "file"(via $LOAD_PATH)require "./file"require_relative "file"Impact
Code that relies on
absolute_pathbeing absolute (e.g.Pathname#relative_path_from) fails withArgumentError: different prefix.