summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomasz Miąsko <tomasz.miasko@gmail.com>2018-04-27 00:00:00 +0000
committerChristoph Reiter <reiter.christoph@gmail.com>2018-07-11 09:16:14 +0200
commita41abe1868a693387cd5cf85567cf2e0fd6c62df (patch)
tree6b02fbbee3a66cd19514e9b032ae13ed849463af
parenteaabc3ba8a827ca1455acc44c1d7c73e2a92bee4 (diff)
downloadgobject-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.py73
-rw-r--r--tests/scanner/Makefile.am1
-rw-r--r--tests/scanner/meson.build1
-rw-r--r--tests/scanner/test_shlibs.py102
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()