Skip to content

Commit 770f1e5

Browse files
committed
Reconsider
1 parent fafed54 commit 770f1e5

File tree

4 files changed

+92
-38
lines changed

4 files changed

+92
-38
lines changed

base/initdefs.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,10 @@ function load_path_expand(env::AbstractString)::Union{String, Nothing}
307307
end
308308
end
309309
isempty(DEPOT_PATH) && return nothing
310-
return abspath(DEPOT_PATH[1], "environments", name, project_names[end]; normalize=false)
310+
return abspath(DEPOT_PATH[1], "environments", name, project_names[end]; safe=true)
311311
end
312312
# otherwise, it's a path
313-
path = abspath(env; normalize=false)
313+
path = abspath(env; safe=true)
314314
if isdir(path)
315315
# directory with a project file?
316316
for proj in project_names
@@ -337,7 +337,7 @@ function active_project(search_load_path::Bool=true)
337337
# there are backedges here from abspath(::AbstractString, ::String)
338338
project = project::String
339339
if !isfile_casesensitive(project) && basename(project) project_names
340-
project = abspath(project, "Project.toml"; normalize=false)
340+
project = abspath(project, "Project.toml"; safe=true)
341341
end
342342
return project
343343
end

base/loading.jl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ function project_file_manifest_path(project_file::String)::Union{Nothing,String}
964964
manifest_path = get(cache.project_file_manifest_path, project_file, missing)
965965
manifest_path === missing || return manifest_path
966966
end
967-
dir = abspath(dirname(project_file); normalize=false)
967+
dir = abspath(dirname(project_file); safe=true)
968968
isfile_casesensitive(project_file) || return nothing
969969
d = parsed_toml(project_file)
970970
base_manifest = workspace_manifest(project_file)
@@ -1248,7 +1248,7 @@ function explicit_manifest_entry_load_spec(manifest_file::String, pkg::PkgId, en
12481248
for slug in (version_slug(uuid, hash), version_slug(uuid, hash, 4))
12491249
for depot in DEPOT_PATH
12501250
path = joinpath(depot, "packages", pkg.name, slug)
1251-
ispath(path) && return PkgLoadSpec(entry_path(abspath(path; normalize=false), pkg.name, entryfile), syntax_version)
1251+
ispath(path) && return PkgLoadSpec(entry_path(abspath(path; safe=true), pkg.name, entryfile), syntax_version)
12521252
end
12531253
end
12541254
# no depot contains the package, return missing to stop looking
@@ -2394,9 +2394,9 @@ function _include_dependency!(dep_list::Vector{Any}, track_dependencies::Bool,
23942394
track_content::Bool, path_may_be_dir::Bool)
23952395
prev = source_path(nothing)
23962396
if prev === nothing
2397-
path = abspath(_path)
2397+
path = abspath(_path; safe=true)
23982398
else
2399-
path = joinpath(dirname(prev), _path)
2399+
path = normpath(joinpath(dirname(prev), _path); safe=true)
24002400
end
24012401
if !track_dependencies[]
24022402
if !path_may_be_dir && !isfile(path)
@@ -3194,11 +3194,11 @@ evalfile(path::AbstractString, args::Vector) = evalfile(path, String[args...])
31943194
# Used in Pkg.jl
31953195
function load_path_setup_code(load_path::Bool=true)
31963196
code = """
3197-
append!(empty!(Base.DEPOT_PATH), $(repr(map(x -> abspath(x; normalize=false), DEPOT_PATH))))
3198-
append!(empty!(Base.DL_LOAD_PATH), $(repr(map(x -> abspath(x; normalize=false), DL_LOAD_PATH))))
3197+
append!(empty!(Base.DEPOT_PATH), $(repr(map(x -> abspath(x; safe=true), DEPOT_PATH))))
3198+
append!(empty!(Base.DL_LOAD_PATH), $(repr(map(x -> abspath(x; safe=true), DL_LOAD_PATH))))
31993199
"""
32003200
if load_path
3201-
load_path = map(x -> abspath(x; normalize=false), Base.load_path())
3201+
load_path = map(x -> abspath(x; safe=true), Base.load_path())
32023202
path_sep = Sys.iswindows() ? ';' : ':'
32033203
any(path -> path_sep in path, load_path) &&
32043204
error("LOAD_PATH entries cannot contain $(repr(path_sep))")
@@ -3284,9 +3284,9 @@ function create_expr_cache(pkg::PkgId, input::PkgLoadSpec, output::String, outpu
32843284
@nospecialize internal_stderr internal_stdout
32853285
rm(output, force=true) # Remove file if it exists
32863286
output_o === nothing || rm(output_o, force=true)
3287-
depot_path = String[abspath(x; normalize=false) for x in DEPOT_PATH]
3288-
dl_load_path = String[abspath(x; normalize=false) for x in DL_LOAD_PATH]
3289-
load_path = String[abspath(x; normalize=false) for x in Base.load_path()]
3287+
depot_path = String[abspath(x; safe=true) for x in DEPOT_PATH]
3288+
dl_load_path = String[abspath(x; safe=true) for x in DL_LOAD_PATH]
3289+
load_path = String[abspath(x; safe=true) for x in Base.load_path()]
32903290
# if pkg is a stdlib, append its parent Project.toml to the load path
32913291
triggers = get(EXT_PRIMED, pkg, nothing)
32923292
if triggers !== nothing
@@ -3344,7 +3344,7 @@ function create_expr_cache(pkg::PkgId, input::PkgLoadSpec, output::String, outpu
33443344
Base.track_nested_precomp($(_pkg_str(vcat(Base.precompilation_stack, pkg))))
33453345
Base.loadable_extensions = $(_pkg_str(loadable_exts))
33463346
Base.precompiling_extension = $(loading_extension)
3347-
Base.include_package_for_output($(_pkg_str(pkg)), $(repr(abspath(input.path; normalize=false))), $(repr(input.julia_syntax_version)), $(repr(depot_path)), $(repr(dl_load_path)),
3347+
Base.include_package_for_output($(_pkg_str(pkg)), $(repr(abspath(input.path; safe=true))), $(repr(input.julia_syntax_version)), $(repr(depot_path)), $(repr(dl_load_path)),
33483348
$(repr(load_path)), $(_pkg_str(concrete_deps)), $(repr(source_path(nothing))))
33493349
""")
33503350
close(io.in)

base/path.jl

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -373,45 +373,55 @@ julia> joinpath(["/home/myuser", "example.jl"])
373373
joinpath
374374

375375
"""
376-
normpath(path::AbstractString)::String
376+
normpath(path::AbstractString; safe=false)::String
377377
378-
Normalize a path, removing "." and ".." entries and changing "/" to the canonical path separator
379-
for the system.
378+
Normalize a path, removing "." changing "/" to the canonical path separator for the system.
379+
If `safe` is false, also removes ".." entries.
380+
381+
!!! warning
382+
Normalization with `safe==false` may change the meaning of a path if the
383+
directory immediately preceding a ".." entry is a symbolic link.
384+
For example, if `a` is a symbolic link in `a/../b`, then this path will
385+
resolve to `b` in the link target's parent directory. However,
386+
`normpath("a/../b")` will return `b`, which will resolve `b` in the
387+
current working directory.
388+
389+
!!! compat "Julia 1.14"
390+
The `safe` keyword argument was added in Julia 1.14.
380391
381392
# Examples
382393
```jldoctest
383394
julia> normpath("/home/myuser/../example.jl")
384395
"/home/example.jl"
385396
397+
julia> normpath("/home/myuser/../example.jl"; safe=true)
398+
"/home/myuser/../example.jl"
399+
386400
julia> normpath("Documents/Julia") == joinpath("Documents", "Julia")
387401
true
388402
```
389-
390-
!!! warning
391-
Normalization may change the meaning of a path if the directory immediately
392-
preceding a ".." entry is a symbolic link. For example, if `a` is a symbolic
393-
link in `a/../b`, then this path will resolve to `b` in the link target's
394-
parent directory. However, `normpath("a/../b")` will return `b`, which will
395-
resolve `b` in the current working directory.
396403
"""
397-
function normpath(path::String)
404+
function normpath(path::String; safe=false)
398405
isabs = isabspath(path)
399406
isdir = isdirpath(path)
400407
drive, path = splitdrive(path)
401408
parts = split(path, path_separator_re; keepempty=false)
402409
filter!(!=("."), parts)
403-
while true
404-
clean = true
405-
for j = 1:length(parts)-1
406-
if parts[j] != ".." && parts[j+1] == ".."
407-
deleteat!(parts, j:j+1)
408-
clean = false
409-
break
410+
if !safe
411+
while true
412+
clean = true
413+
for j = 1:length(parts)-1
414+
if parts[j] != ".." && parts[j+1] == ".."
415+
deleteat!(parts, j:j+1)
416+
clean = false
417+
break
418+
end
410419
end
420+
clean && break
411421
end
412-
clean && break
413422
end
414423
if isabs
424+
# N.B.: `..` at the beginning of a path may always be removed
415425
while !isempty(parts) && parts[1] == ".."
416426
popfirst!(parts)
417427
end
@@ -434,10 +444,10 @@ end
434444
Convert a set of paths to a normalized path by joining them together and removing
435445
"." and ".." entries. Equivalent to `normpath(joinpath(path, paths...))`.
436446
"""
437-
normpath(a::AbstractString, b::AbstractString...) = normpath(joinpath(a,b...))
447+
normpath(a::AbstractString, b::AbstractString...; kwargs...) = normpath(joinpath(a,b...); kwargs...)
438448

439449
"""
440-
abspath(path::AbstractString; normalize=true)::String
450+
abspath(path::AbstractString; safe=false)::String
441451
442452
Convert a path to an absolute path by adding the current directory if necessary.
443453
If `normalize` is true, also normalizes the path as in [`normpath`](@ref).
@@ -453,9 +463,9 @@ Which gives a path like `"/home/JuliaUser/data/"`.
453463
See also [`joinpath`](@ref), [`pwd`](@ref), [`expanduser`](@ref).
454464
455465
!!! compat "Julia 1.14"
456-
The `normalize` keyword argument was added in Julia 1.14.
466+
The `safe` keyword argument was added in Julia 1.14.
457467
"""
458-
function abspath(a::String; normalize=true)::String
468+
function abspath(a::String; safe=false)::String
459469
if !isabspath(a)
460470
cwd = pwd()
461471
a_drive, a_nodrive = splitdrive(a)
@@ -466,7 +476,7 @@ function abspath(a::String; normalize=true)::String
466476
a = joinpath(cwd, a)
467477
end
468478
end
469-
return normalize ? normpath(a) : a
479+
return normpath(a; safe)
470480
end
471481

472482
"""

test/loading.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,3 +1915,47 @@ module M58272_to end
19151915
copy!(LOAD_PATH, old_load_path)
19161916
end
19171917
end
1918+
1919+
# Test loading when project or depot paths contain the symlink/.. pattern
1920+
if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER
1921+
@testset "symlink/.. in project or depot paths" begin
1922+
mktempdir() do dir
1923+
old_load_path = copy(LOAD_PATH)
1924+
old_active_project = Base.ACTIVE_PROJECT[]
1925+
1926+
# Create a symlinked subdirectory
1927+
symlink_dir = joinpath(dir, "project_symlink")
1928+
target_dir = joinpath(@__DIR__, "project", "deps", "Foo1", "src")
1929+
mkpath(target_dir)
1930+
symlink(target_dir, symlink_dir)
1931+
symlink_parent = joinpath(symlink_dir, "..")
1932+
try
1933+
# test symlink'd project path
1934+
Base.set_active_project(symlink_parent)
1935+
push!(empty!(LOAD_PATH), "@")
1936+
1937+
Foo1_uuid = Base.UUID("1a6589dc-c33c-4d54-9a54-f7fc4b3ff616")
1938+
id = Base.identify_package("Foo")
1939+
@test id.uuid == Foo1_uuid
1940+
located = Base.locate_package(id)
1941+
@test located !== nothing
1942+
@test samefile(located, joinpath(target_dir, "Foo.jl"))
1943+
1944+
# Test symlink'd depot path
1945+
symlink_depot = joinpath(dir, "depot_symlink")
1946+
target_dir = joinpath(@__DIR__, "depot", "packages")
1947+
symlink(target_dir, symlink_depot)
1948+
push!(empty!(LOAD_PATH), joinpath(@__DIR__, "project"))
1949+
push!(empty!(DEPOT_PATH), joinpath(symlink_depot, ".."))
1950+
pkg = Base.identify_package(
1951+
Base.PkgId(Base.UUID("2a550a13-6bab-4a91-a4ee-dff34d6b99d0"), "Bar"), "Baz")
1952+
@test pkg !== nothing
1953+
@test samefile(Base.locate_package(pkg), joinpath(target_dir, "Baz", "81oLe", "src", "Baz.jl"))
1954+
finally
1955+
Base.ACTIVE_PROJECT[] = old_active_project
1956+
copy!(LOAD_PATH, old_load_path)
1957+
copy!(DEPOT_PATH, original_depot_path)
1958+
end
1959+
end
1960+
end
1961+
end

0 commit comments

Comments
 (0)