• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright (c) 2017 Google Inc.
3
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Checks names of global exports from a library."""
16
17import os.path
18import re
19import subprocess
20import sys
21
22
23PROG = 'check_symbol_exports'
24
25
26def command_output(cmd, directory):
27    """Runs a command in a directory and returns its standard output stream.
28
29    Captures the standard error stream.
30
31    Raises a RuntimeError if the command fails to launch or otherwise fails.
32    """
33    p = subprocess.Popen(cmd,
34                         cwd=directory,
35                         stdout=subprocess.PIPE,
36                         stderr=subprocess.PIPE,
37                         universal_newlines=True)
38    (stdout, _) = p.communicate()
39    if p.returncode != 0:
40        raise RuntimeError('Failed to run %s in %s' % (cmd, directory))
41    return stdout
42
43
44def check_library(library):
45    """Scans the given library file for global exports.  If all such
46    exports are namespaced or begin with spv (in either C or C++ styles)
47    then return 0.  Otherwise emit a message and return 1."""
48
49    # The pattern for a global symbol record
50    symbol_pattern = re.compile(r'^[0-aA-Fa-f]+ g *F \.text.*[0-9A-Fa-f]+ +(.*)')
51
52    # Ok patterns are as follows, assuming Itanium name mangling:
53    #   spv[A-Z]          :  extern "C" symbol starting with spv
54    #   _ZN               :  something in a namespace
55    #   _Z[0-9]+spv[A-Z_] :  C++ symbol starting with spv[A-Z_]
56    symbol_ok_pattern = re.compile(r'^(spv[A-Z]|_ZN|_Z[0-9]+spv[A-Z_])')
57
58    # In addition, the following pattern whitelists global functions that are added
59    # by the protobuf compiler:
60    #   - AddDescriptors_spvtoolsfuzz_2eproto()
61    #   - InitDefaults_spvtoolsfuzz_2eproto()
62    symbol_whitelist_pattern = re.compile(r'_Z[0-9]+(InitDefaults|AddDescriptors)_spvtoolsfuzz_2eprotov')
63
64    seen = set()
65    result = 0
66    for line in command_output(['objdump', '-t', library], '.').split('\n'):
67        match = symbol_pattern.search(line)
68        if match:
69            symbol = match.group(1)
70            if symbol not in seen:
71                seen.add(symbol)
72                #print("look at '{}'".format(symbol))
73                if not (symbol_whitelist_pattern.match(symbol) or symbol_ok_pattern.match(symbol)):
74                    print('{}: error: Unescaped exported symbol: {}'.format(PROG, symbol))
75                    result = 1
76    return result
77
78
79def main():
80    import argparse
81    parser = argparse.ArgumentParser(description='Check global names exported from a library')
82    parser.add_argument('library', help='The static library to examine')
83    args = parser.parse_args()
84
85    if not os.path.isfile(args.library):
86        print('{}: error: {} does not exist'.format(PROG, args.library))
87        sys.exit(1)
88
89    if os.name == 'posix':
90        status = check_library(args.library)
91        sys.exit(status)
92    else:
93        print('Passing test since not on Posix')
94        sys.exit(0)
95
96
97if __name__ == '__main__':
98    main()
99