Skip to content

Commit af7c08f

Browse files
committed
wip - Ensure symlinked files are not loaded twice
TODO: - changelog - require (not relative) specs - other specs? closes oracle#3138
1 parent 099ae3f commit af7c08f

File tree

4 files changed

+40
-3
lines changed

4 files changed

+40
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Compatibility:
3030
* Add `Refinement#refined_class` (#3039, @itarato).
3131
* Add `rb_hash_new_capa` function (#3039, @itarato).
3232
* Fix `Encoding::Converter#primitive_convert` and raise `FrozenError` when a destination buffer argument is frozen (@andrykonchin).
33+
* Ensure symlinked paths aren't loaded more than once (@rwstauner).
3334

3435
Performance:
3536

spec/ruby/core/kernel/require_relative_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,24 @@
235235
features.should_not include(canonical_path)
236236
end
237237

238+
ruby_version_is "3.1" do
239+
it "does not require symlinked target file twice" do
240+
symlink_path = "#{@symlink_basename}/load_fixture.rb"
241+
absolute_path = "#{tmp("")}#{symlink_path}"
242+
canonical_path = "#{CODE_LOADING_DIR}/load_fixture.rb"
243+
touch(@requiring_file) { |f|
244+
f.puts "require_relative #{symlink_path.inspect}"
245+
f.puts "require_relative #{canonical_path.inspect}"
246+
}
247+
load(@requiring_file)
248+
ScratchPad.recorded.should == [:loaded]
249+
250+
features = $LOADED_FEATURES.select { |path| path.end_with?('load_fixture.rb') }
251+
features.should include(absolute_path)
252+
features.should_not include(canonical_path)
253+
end
254+
end
255+
238256
it "stores the same path that __FILE__ returns in the required file" do
239257
symlink_path = "#{@symlink_basename}/load_fixture_and__FILE__.rb"
240258
touch(@requiring_file) { |f|

src/main/ruby/truffleruby/core/kernel.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def autoload?(name)
231231
when :feature_loaded
232232
false
233233
when :feature_found
234-
Primitive.load_feature(feature, path)
234+
Truffle::FeatureLoader.load_unless_realpath_loaded(feature, path)
235235
when :not_found
236236
raise Truffle::KernelOperations.load_error(feature)
237237
end
@@ -257,7 +257,7 @@ def require(feature)
257257
when :feature_loaded
258258
false
259259
when :feature_found
260-
Primitive.load_feature(feature, path)
260+
Truffle::FeatureLoader.load_unless_realpath_loaded(feature, path)
261261
when :not_found
262262
if lazy_rubygems
263263
Truffle::KernelOperations.loading_rubygems = true
@@ -290,7 +290,7 @@ def require_relative(feature)
290290
false
291291
when :feature_found
292292
# The first argument needs to be the expanded path here for patching to work correctly
293-
Primitive.load_feature(path, path)
293+
Truffle::FeatureLoader.load_unless_realpath_loaded(path, path)
294294
when :not_found
295295
raise Truffle::KernelOperations.load_error(feature)
296296
end

src/main/ruby/truffleruby/core/truffle/feature_loader.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ module FeatureLoader
3535
# A snapshot of $LOADED_FEATURES, to check if the @loaded_features_index cache is up to date.
3636
@loaded_features_version = -1
3737

38+
@features_realpath_cache = {}
39+
@loaded_features_realpaths = {}
40+
3841
@expanded_load_path = []
3942
# A snapshot of $LOAD_PATH, to check if the @expanded_load_path cache is up to date.
4043
@load_path_version = -1
@@ -140,6 +143,15 @@ def self.find_feature_or_file(feature, use_feature_provided = true)
140143
end
141144
end
142145

146+
def self.load_unless_realpath_loaded(feature, path)
147+
realpath = (@features_realpath_cache[path] ||= File.realpath(path) || path)
148+
return false if @loaded_features_realpaths.key?(realpath)
149+
150+
result = Primitive.load_feature(feature, path)
151+
@loaded_features_realpaths[realpath] = path
152+
result
153+
end
154+
143155
def self.expanded_path_provided(path, ext, use_feature_provided)
144156
if use_feature_provided && feature_provided?(path, true)
145157
[:feature_loaded, path, ext]
@@ -292,13 +304,19 @@ def self.get_loaded_features_index
292304
unless @loaded_features_version == $LOADED_FEATURES.version
293305
raise '$LOADED_FEATURES is frozen; cannot append feature' if $LOADED_FEATURES.frozen?
294306
@loaded_features_index.clear
307+
previous_realpaths = @features_realpath_cache.dup
308+
@features_realpath_cache.clear
309+
@loaded_features_realpaths.clear
295310
$LOADED_FEATURES.map! do |val|
296311
val = StringValue(val)
297312
#val.freeze # TODO freeze these but post-boot.rb issue using replace
298313
val
299314
end
300315
$LOADED_FEATURES.each_with_index do |val, idx|
301316
features_index_add(val, idx)
317+
realpath = previous_realpaths[val] || (File.exist?(val) ? File.realpath(val) : val)
318+
@features_realpath_cache[val] = realpath
319+
@loaded_features_realpaths[val] = realpath
302320
end
303321
@loaded_features_version = $LOADED_FEATURES.version
304322
end

0 commit comments

Comments
 (0)