linkage_checker: check variable references with dlopen

The linkage check currently does nothing to check the validity of
variable-referenced libraries (prefixed with an `@`).

We could rectify that by mimicking the dynamic linker in looking up the
variable-referenced library, but this could get quite complicated.
Instead, let's let the linker do the hard work by checking if we can
`dlopen` libraries and bundles that contain variable linkage. The
`dlopen` will fail if the linker cannot resolve the variable reference.

There are at least two disadvantages to this approach relative to the
alternative suggested above:
1. This doesn't work for binary executables.
2. This doesn't identify which variable references are broken.

It's still better than not checking them at all, which is what we do
currently, and saves us from having to carry around code that parses and
verifies variable references directly.
This commit is contained in:
Carlo Cabrera 2021-11-15 02:24:16 +08:00
parent 1f0ab4a1ee
commit 0191a275cc
No known key found for this signature in database
GPG Key ID: C74D447FC549A1D0

View File

@ -32,6 +32,7 @@ class LinkageChecker
@unnecessary_deps = [] @unnecessary_deps = []
@unwanted_system_dylibs = [] @unwanted_system_dylibs = []
@version_conflict_deps = [] @version_conflict_deps = []
@broken_variable_dylibs = []
check_dylibs(rebuild_cache: rebuild_cache) check_dylibs(rebuild_cache: rebuild_cache)
end end
@ -46,6 +47,7 @@ class LinkageChecker
display_items "Undeclared dependencies with linkage", @undeclared_deps display_items "Undeclared dependencies with linkage", @undeclared_deps
display_items "Dependencies with no linkage", @unnecessary_deps display_items "Dependencies with no linkage", @unnecessary_deps
display_items "Unwanted system libraries", @unwanted_system_dylibs display_items "Unwanted system libraries", @unwanted_system_dylibs
display_items "Libraries with broken variable references", @broken_variable_dylibs
end end
def display_reverse_output def display_reverse_output
@ -68,11 +70,12 @@ class LinkageChecker
display_items "Broken dependencies", @broken_deps, puts_output: puts_output display_items "Broken dependencies", @broken_deps, puts_output: puts_output
display_items "Unwanted system libraries", @unwanted_system_dylibs, puts_output: puts_output display_items "Unwanted system libraries", @unwanted_system_dylibs, puts_output: puts_output
display_items "Conflicting libraries", @version_conflict_deps, puts_output: puts_output display_items "Conflicting libraries", @version_conflict_deps, puts_output: puts_output
display_items "Libraries with broken variable references", @broken_variable_dylibs, puts_output: puts_output
end end
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def broken_library_linkage? def broken_library_linkage?
issues = [@broken_deps, @unwanted_system_dylibs, @version_conflict_deps] issues = [@broken_deps, @unwanted_system_dylibs, @version_conflict_deps, @broken_variable_dylibs]
[issues, unexpected_broken_dylibs, unexpected_present_dylibs].flatten.any?(&:present?) [issues, unexpected_broken_dylibs, unexpected_present_dylibs].flatten.any?(&:present?)
end end
@ -148,6 +151,7 @@ class LinkageChecker
end end
checked_dylibs = Set.new checked_dylibs = Set.new
dlopened_if_needed_files = Set.new
keg_files_dylibs.each do |file, dylibs| keg_files_dylibs.each do |file, dylibs|
dylibs.each do |dylib| dylibs.each do |dylib|
@ -159,6 +163,15 @@ class LinkageChecker
if dylib.start_with? "@" if dylib.start_with? "@"
@variable_dylibs << dylib @variable_dylibs << dylib
if dlopened_if_needed_files.add?(file)
file = Pathname.new(file)
next if file.binary_executable?
next if dylib_found_via_dlopen(file.dylib_id)
@broken_variable_dylibs << file.dylib_id
end
next next
end end