Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a warning against having multiple copies of a shared library loaded #43007

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,6 @@ include("version.jl")

# system & environment
include("sysinfo.jl")
include("libc.jl")
using .Libc: getpid, gethostname, time

# Logging
include("logging.jl")
Expand All @@ -304,6 +302,10 @@ include("task.jl")
include("threads_overloads.jl")
include("weakkeydict.jl")

# Libc and Libdl
include("libc.jl")
using .Libc: getpid, gethostname, time

include("env.jl")

# functions defined in Random
Expand Down
74 changes: 68 additions & 6 deletions base/libdl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,54 @@ function dlsym_e(hnd::Ptr, s::Union{Symbol,AbstractString})
return something(dlsym(hnd, s; throw_error=false), C_NULL)
end

if Sys.isapple()
const dlpattern = r"^([^.]+).*\.dylib$"
elseif Sys.iswindows()
const dlpattern = r"^(.+)\.dll$"
else
#assume Sys.islinux, or similar
const dlpattern = r"^(.+?)\.so(?:\..*)?$"
end

# Returns the name of the library.
function _dlname(path)
bn = basename(path)
m = match(dlpattern, bn)
return isnothing(m) ? bn : m.captures[1]
end

# Returns absolute path without symbolic links.
_dlabspath(x) = isfile(x) ? abspath(realpath(x)) : x

const _dlname_cache = Dict{String, String}()
const _suppressed_warnings = Set{String}()

# Checks if the same shared library is loaded from two different files.
function _check_dllist()
paths = dllist()
names = [get!(() -> _dlname(x), _dlname_cache, x) for x in paths]
dict = Dict{String, String}() # name => path
for (name, path) in zip(names, paths)
# Test if there is a duplicity (name already in `dict`)
oldpath = get!(dict, name, path)
path == oldpath && continue
# Test if already suppressed
name ∈ _suppressed_warnings && continue
# Show the warning only once (suppress in the future)
push!(_suppressed_warnings, name)
# Test if absolute paths are equal
_dlabspath(path) == _dlabspath(oldpath) && continue
@warn """Detected possible duplicate library loaded: $(name)
This may lead to unexpected behavior!
$(path)
$(oldpath)
To suppress this warning, you can use the following argument:
dlopen([name_or_path]; suppress_warnings = ["$(name)"])"""
end
end

"""
dlopen(libfile::AbstractString [, flags::Integer]; throw_error:Bool = true)
dlopen(libfile::AbstractString [, flags::Integer]; throw_error:Bool = true, suppress_warnings::Vector{String} = String[])

Load a shared library, returning an opaque handle.

Expand Down Expand Up @@ -107,18 +153,34 @@ If the library cannot be found, this method throws an error, unless the keyword
From Julia 1.6 on, this method replaces paths starting with `@executable_path/` with
the path to the Julia executable, allowing for relocatable relative-path loads. In
Julia 1.5 and earlier, this only worked on macOS.

!!! note
From Julia 1.8 on, this method detects when a library with the same name is loaded
multiple times from different files because it may lead to unexpected behavior. In such
a case, the method produces a warning. The warning be suppressed by adding the name of
the library into a newly introduced keyword argument `suppress_warnings`.
"""
function dlopen end

const _dlopen_lock = ReentrantLock()

dlopen(s::Symbol, flags::Integer = RTLD_LAZY | RTLD_DEEPBIND; kwargs...) =
dlopen(string(s), flags; kwargs...)

function dlopen(s::AbstractString, flags::Integer = RTLD_LAZY | RTLD_DEEPBIND; throw_error::Bool = true)
ret = ccall(:jl_load_dynamic_library, Ptr{Cvoid}, (Cstring,UInt32,Cint), s, flags, Cint(throw_error))
if ret == C_NULL
return nothing
function dlopen(s::AbstractString, flags::Integer = RTLD_LAZY | RTLD_DEEPBIND; throw_error::Bool = true, suppress_warnings::Vector{String} = String[])
@lock _dlopen_lock begin
union!(_suppressed_warnings, suppress_warnings)
if isempty(s) # Do not load anything, but run _check_dllist()
_check_dllist()
return nothing
end
ret = ccall(:jl_load_dynamic_library, Ptr{Cvoid}, (Cstring,UInt32,Cint), s, flags, Cint(throw_error))
if ret == C_NULL
return nothing
end
_check_dllist()
return ret
end
return ret
end

"""
Expand Down