• 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"""Ensures that all externally visible functions in the library have an appropriate name
16
17Appropriate function names are:
18  - names starting with spv,
19  - anything in a namespace,
20  - functions added by the protobuf compiler,
21  - and weak definitions of new and delete."""
22
23import os.path
24import re
25import subprocess
26import sys
27
28
29PROG = 'check_symbol_exports'
30
31
32def command_output(cmd, directory):
33    """Runs a command in a directory and returns its standard output stream.
34
35    Captures the standard error stream.
36
37    Raises a RuntimeError if the command fails to launch or otherwise fails.
38    """
39    p = subprocess.Popen(cmd,
40                         cwd=directory,
41                         stdout=subprocess.PIPE,
42                         stderr=subprocess.PIPE,
43                         universal_newlines=True)
44    (stdout, _) = p.communicate()
45    if p.returncode != 0:
46        raise RuntimeError('Failed to run %s in %s' % (cmd, directory))
47    return stdout
48
49
50def check_library(library):
51    """Scans the given library file for global exports.  If all such
52    exports are namespaced or begin with spv (in either C or C++ styles)
53    then return 0.  Otherwise emit a message and return 1."""
54
55    # The pattern for an externally visible symbol record
56    symbol_pattern = re.compile(r'^[0-aA-Fa-f]+ +([wg]) *F \.text.*[0-9A-Fa-f]+ +(.*)')
57
58    # Ok patterns are as follows, assuming Itanium name mangling:
59    #   spv[A-Z]          :  extern "C" symbol starting with spv
60    #   _ZN               :  something in a namespace
61    #   _ZSt              :  something in the standard namespace
62    #   _ZZN              :  something in a local scope and namespace
63    #   _Z[0-9]+spv[A-Z_] :  C++ symbol starting with spv[A-Z_]
64    symbol_ok_pattern = re.compile(r'^(spv[A-Z]|_ZN|_ZSt|_ZZN|_Z[0-9]+spv[A-Z_])')
65
66    # In addition, the following pattern allowlists global functions that are added
67    # by the protobuf compiler:
68    #   - AddDescriptors_spvtoolsfuzz_2eproto()
69    #   - InitDefaults_spvtoolsfuzz_2eproto()
70    symbol_allowlist_pattern = re.compile(r'_Z[0-9]+(InitDefaults|AddDescriptors)_spvtoolsfuzz_2eprotov')
71
72    symbol_is_new_or_delete = re.compile(r'^(_Zna|_Znw|_Zdl|_Zda)')
73    # Compilaion for Arm has various thunks for constructors, destructors, vtables.
74    # They are weak.
75    symbol_is_thunk = re.compile(r'^_ZT')
76
77    # This occurs in NDK builds.
78    symbol_is_hidden = re.compile(r'^\.hidden ')
79
80    seen = set()
81    result = 0
82    for line in command_output(['objdump', '-t', library], '.').split('\n'):
83        match = symbol_pattern.search(line)
84        if match:
85            linkage = match.group(1)
86            symbol = match.group(2)
87            if symbol not in seen:
88                seen.add(symbol)
89                #print("look at '{}'".format(symbol))
90                if not (symbol_is_new_or_delete.match(symbol) and linkage == 'w'):
91                    if not (symbol_is_thunk.match(symbol) and linkage == 'w'):
92                        if not (symbol_allowlist_pattern.match(symbol) or
93                                symbol_ok_pattern.match(symbol) or
94                                symbol_is_hidden.match(symbol)):
95                            print('{}: error: Unescaped exported symbol: {}'.format(PROG, symbol))
96                            result = 1
97    return result
98
99
100def main():
101    import argparse
102    parser = argparse.ArgumentParser(description='Check global names exported from a library')
103    parser.add_argument('library', help='The static library to examine')
104    args = parser.parse_args()
105
106    if not os.path.isfile(args.library):
107        print('{}: error: {} does not exist'.format(PROG, args.library))
108        sys.exit(1)
109
110    if os.name == 'posix':
111        status = check_library(args.library)
112        sys.exit(status)
113    else:
114        print('Passing test since not on Posix')
115        sys.exit(0)
116
117
118if __name__ == '__main__':
119    main()
120