diff options
author | Tomasz Miąsko <tomasz.miasko@gmail.com> | 2018-04-27 00:00:00 +0000 |
---|---|---|
committer | Christoph Reiter <reiter.christoph@gmail.com> | 2018-07-11 09:16:14 +0200 |
commit | a41abe1868a693387cd5cf85567cf2e0fd6c62df (patch) | |
tree | 6b02fbbee3a66cd19514e9b032ae13ed849463af | |
parent | eaabc3ba8a827ca1455acc44c1d7c73e2a92bee4 (diff) | |
download | gobject-introspection-a41abe1868a693387cd5cf85567cf2e0fd6c62df.tar.gz |
Avoid accidental library name matches when parsing ldd output.
* Use a single pattern that matches against potentially complete paths.
* Extract filename only afterwards on platforms where it is necessary.
* Match patterns against complete words in ldd output instead of searching
for them inside the lines - this avoids unintentional matches without
complexity of negative lookbehinds and negative lookaheads.
Fixes issue #208.
-rw-r--r-- | giscanner/shlibs.py | 73 | ||||
-rw-r--r-- | tests/scanner/Makefile.am | 1 | ||||
-rw-r--r-- | tests/scanner/meson.build | 1 | ||||
-rw-r--r-- | tests/scanner/test_shlibs.py | 102 |
4 files changed, 144 insertions, 33 deletions
diff --git a/giscanner/shlibs.py b/giscanner/shlibs.py index 525bdba2..32fc5e44 100644 --- a/giscanner/shlibs.py +++ b/giscanner/shlibs.py @@ -49,19 +49,17 @@ def _resolve_libtool(options, binary, libraries): # libpangoft2-1.0.so.0 => /usr/lib/libpangoft2-1.0.so.0 (0x006c1000) # # We say that if something in the output looks like libpangoft2<blah> -# then the *first* such in the output is the soname. We require <blah> -# to start with [^A-Za-z0-9_-] to avoid problems with libpango vs libpangoft2 -# -# The negative lookbehind at the start is to avoid problems if someone -# is crazy enough to name a library liblib<foo> when lib<foo> exists. -# -# Match absolute paths on OS X to conform to how libraries are usually -# referenced on OS X systems. +# then the *first* such in the output is the soname. def _ldd_library_pattern(library_name): - pattern = "(?<![A-Za-z0-9_-])(lib*%s[^A-Za-z0-9_-][^\s\(\)]*)" - if platform.system() == 'Darwin': - pattern = "([^\s]*lib*%s[^A-Za-z0-9_-][^\s\(\)]*)" - return re.compile(pattern % re.escape(library_name)) + return re.compile(r"""^ + # Require trailing slash to avoid matching liblibfoo when looking for libfoo. + (.*[/])? + lib%s + # Prohibit library name characters to avoid matching libpangoft2 when looking for libpango. + [^/A-Za-z0-9_-] + # Anything but the path separator to avoid matching directories. + [^/]* + $""" % re.escape(library_name), re.VERBOSE) # This is a what we do for non-la files. We assume that we are on an @@ -96,8 +94,7 @@ def _resolve_non_libtool(options, binary, libraries): if host_os() == 'nt': cc = CCompiler() - shlibs = cc.resolve_windows_libs(libraries, options) - + return cc.resolve_windows_libs(libraries, options) else: args = [] libtool = get_libtool_command(options) @@ -109,31 +106,41 @@ def _resolve_non_libtool(options, binary, libraries): args.extend(['otool', '-L', binary.args[0]]) else: args.extend(['ldd', binary.args[0]]) - proc = subprocess.Popen(args, stdout=subprocess.PIPE) - patterns = {} - for library in libraries: - patterns[library] = _ldd_library_pattern(library) - - shlibs = [] - for line in proc.stdout: - line = line.decode('ascii') - # ldd on *BSD show the argument passed on the first line even if - # there is only one argument. We have to ignore it because it is - # possible for the name of the binary to match _ldd_library_pattern. - if line == binary.args[0] + ':\n': - continue + output = subprocess.check_output(args, universal_newlines=True, errors='replace') + + # Use absolute paths on OS X to conform to how libraries are usually + # referenced on OS X systems, and file names everywhere else. + basename = platform.system() != 'Darwin' + return resolve_from_ldd_output(libraries, output, basename=basename) + + +def resolve_from_ldd_output(libraries, output, basename=False): + patterns = {} + for library in libraries: + patterns[library] = _ldd_library_pattern(library) + + shlibs = [] + for line in output.splitlines(): + # ldd on *BSD show the argument passed on the first line even if + # there is only one argument. We have to ignore it because it is + # possible for the name of the binary to match _ldd_library_pattern. + if line.endswith(':'): + continue + for word in line.split(): for library, pattern in patterns.items(): - m = pattern.search(line) + m = pattern.match(word) if m: del patterns[library] - shlibs.append(m.group(1)) + shlibs.append(m.group()) break - if len(patterns) > 0: - raise SystemExit( - "ERROR: can't resolve libraries to shared libraries: " + - ", ".join(patterns.keys())) + if len(patterns) > 0: + raise SystemExit( + "ERROR: can't resolve libraries to shared libraries: " + + ", ".join(patterns.keys())) + if basename: + shlibs = list(map(os.path.basename, shlibs)) return shlibs diff --git a/tests/scanner/Makefile.am b/tests/scanner/Makefile.am index 6b705f5d..279addfe 100644 --- a/tests/scanner/Makefile.am +++ b/tests/scanner/Makefile.am @@ -229,6 +229,7 @@ CHECKDOCS = endif PYTESTS = \ + test_shlibs.py \ test_sourcescanner.py \ test_transformer.py diff --git a/tests/scanner/meson.build b/tests/scanner/meson.build index f8b794d2..3458c32e 100644 --- a/tests/scanner/meson.build +++ b/tests/scanner/meson.build @@ -3,6 +3,7 @@ scanner_test_env = environment() scanner_test_env.append('PYTHONPATH', join_paths(meson.current_build_dir(), '../../')) scanner_test_files = [ + 'test_shlibs.py', 'test_sourcescanner.py', 'test_transformer.py' ] diff --git a/tests/scanner/test_shlibs.py b/tests/scanner/test_shlibs.py new file mode 100644 index 00000000..c3e28892 --- /dev/null +++ b/tests/scanner/test_shlibs.py @@ -0,0 +1,102 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest + +from giscanner.shlibs import resolve_from_ldd_output + + +class TestLddParser(unittest.TestCase): + + def test_resolve_from_ldd_output(self): + output = '''\ + libglib-2.0.so.0 => /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x00007fbe12d68000) + libgtk-3.so.0 => /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 (0x00007fbe12462000) + libgdk-3.so.0 => /usr/lib/x86_64-linux-gnu/libgdk-3.so.0 (0x00007fbe1216c000) + libpango-1.0.so.0 => /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 (0x00007fbe11d1a000) + libatk-1.0.so.0 => /usr/lib/x86_64-linux-gnu/libatk-1.0.so.0 (0x00007fbe11af4000)''' + libraries = ['glib-2.0', 'gtk-3', 'pango-1.0'] + + self.assertEqual( + ['libglib-2.0.so.0', 'libgtk-3.so.0', 'libpango-1.0.so.0'], + resolve_from_ldd_output(libraries, output)) + + def test_unresolved_library(self): + output = '' + libraries = ['foo'] + + with self.assertRaises(SystemExit, msg='can\'t resolve libraries to shared libraries: foo'): + resolve_from_ldd_output(libraries, output) + + def test_prefixed_library_name(self): + output = '''\ + /usr/lib/liblibX.so + /usr/lib/libX.so''' + + self.assertEqual( + ['/usr/lib/libX.so'], + resolve_from_ldd_output(['X'], output)) + self.assertEqual( + ['/usr/lib/liblibX.so'], + resolve_from_ldd_output(['libX'], output)) + + def test_suffixed_library_name(self): + output = '''\ + libpangocairo.so.0 => /usr/lib/x86_64-linux-gnu/libpangocairo.so.0 (0x00) + libpangoft2.so.0 => /usr/lib/x86_64-linux-gnu/libpangoft2.so.0 (0x00) + libpango.so.0 => /usr/lib/x86_64-linux-gnu/libpango.so.0 (0x00)''' + libraries = ['pango'] + + self.assertEqual( + ['libpango.so.0'], + resolve_from_ldd_output(libraries, output)) + + def test_header_is_ignored(self): + output = '''/tmp-introspection/libfoo.so.999: + 0000000000000000 0000000000000000 rlib 0 3 0 /usr/local/lib/libfoo.so.1''' + libraries = ['foo'] + + self.assertEqual( + ['/usr/local/lib/libfoo.so.1'], + resolve_from_ldd_output(libraries, output)) + + def test_executable_path_includes_library_name(self): + """ + Regression test for https://gitlab.gnome.org/GNOME/gobject-introspection/issues/208 + """ + output = '''/usr/ports/pobj/libgepub-0.6.0/build-amd64/tmp-introspectnxmyodg1/Gepub-0.6: + Start End Type Open Ref GrpRef Name + 00001066c8400000 00001066c8605000 exe 2 0 0 /usr/ports/pobj/libgepub-0.6.0/build-amd64/tmp-introspectnxmyodg1/Gepub-0.6 + 000010690019c000 00001069003a8000 rlib 0 1 0 /usr/local/lib/libgepub-0.6.so.0.0''' + libraries = ['gepub-0.6'] + + self.assertEqual( + ['/usr/local/lib/libgepub-0.6.so.0.0'], + resolve_from_ldd_output(libraries, output)) + + def test_library_path_includes_library_name(self): + output = '''/usr/ports/pobj/gnome-music-3.28.1/build-amd64/tmp-introspectuz5xaun3/Gd-1.0: + Start End Type Open Ref GrpRef Name + 0000070e40f00000 0000070e41105000 exe 2 0 0 /usr/ports/pobj/gnome-music-3.28.1/build-amd64/tmp-introspectuz5xaun3/Gd-1.0 + 00000710f9b39000 00000710f9d51000 rlib 0 1 0 /usr/ports/pobj/gnome-music-3.28.1/build-amd64/subprojects/libgd/libgd/libgd.so''' + libraries = ['gd'] + + self.assertEqual( + ['/usr/ports/pobj/gnome-music-3.28.1/build-amd64/subprojects/libgd/libgd/libgd.so'], + resolve_from_ldd_output(libraries, output)) + + def test_basename(self): + output = '''/usr/lib/libfoo.so''' + + self.assertEqual( + ['/usr/lib/libfoo.so'], + resolve_from_ldd_output(['foo'], output, basename=False)) + self.assertEqual( + ['libfoo.so'], + resolve_from_ldd_output(['foo'], output, basename=True)) + + +if __name__ == '__main__': + unittest.main() |