#!/usr/bin/env python # Copyright (c) 2017 Google Inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Ensures that all externally visible functions in the library have an appropriate name Appropriate function names are: - names starting with spv, - anything in a namespace, - functions added by the protobuf compiler, - and weak definitions of new and delete.""" import os.path import re import subprocess import sys PROG = 'check_symbol_exports' def command_output(cmd, directory): """Runs a command in a directory and returns its standard output stream. Captures the standard error stream. Raises a RuntimeError if the command fails to launch or otherwise fails. """ p = subprocess.Popen(cmd, cwd=directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) (stdout, _) = p.communicate() if p.returncode != 0: raise RuntimeError('Failed to run %s in %s' % (cmd, directory)) return stdout def check_library(library): """Scans the given library file for global exports. If all such exports are namespaced or begin with spv (in either C or C++ styles) then return 0. Otherwise emit a message and return 1.""" # The pattern for an externally visible symbol record symbol_pattern = re.compile(r'^[0-aA-Fa-f]+ +([wg]) *F \.text.*[0-9A-Fa-f]+ +(.*)') # Ok patterns are as follows, assuming Itanium name mangling: # spv[A-Z] : extern "C" symbol starting with spv # _ZN : something in a namespace # _ZSt : something in the standard namespace # _ZZN : something in a local scope and namespace # _Z[0-9]+spv[A-Z_] : C++ symbol starting with spv[A-Z_] symbol_ok_pattern = re.compile(r'^(spv[A-Z]|_ZN|_ZSt|_ZZN|_Z[0-9]+spv[A-Z_])') # In addition, the following pattern allowlists global functions that are added # by the protobuf compiler: # - AddDescriptors_spvtoolsfuzz_2eproto() # - InitDefaults_spvtoolsfuzz_2eproto() symbol_allowlist_pattern = re.compile(r'_Z[0-9]+(InitDefaults|AddDescriptors)_spvtoolsfuzz_2eprotov') symbol_is_new_or_delete = re.compile(r'^(_Zna|_Znw|_Zdl|_Zda)') # Compilaion for Arm has various thunks for constructors, destructors, vtables. # They are weak. symbol_is_thunk = re.compile(r'^_ZT') # This occurs in NDK builds. symbol_is_hidden = re.compile(r'^\.hidden ') seen = set() result = 0 for line in command_output(['objdump', '-t', library], '.').split('\n'): match = symbol_pattern.search(line) if match: linkage = match.group(1) symbol = match.group(2) if symbol not in seen: seen.add(symbol) #print("look at '{}'".format(symbol)) if not (symbol_is_new_or_delete.match(symbol) and linkage == 'w'): if not (symbol_is_thunk.match(symbol) and linkage == 'w'): if not (symbol_allowlist_pattern.match(symbol) or symbol_ok_pattern.match(symbol) or symbol_is_hidden.match(symbol)): print('{}: error: Unescaped exported symbol: {}'.format(PROG, symbol)) result = 1 return result def main(): import argparse parser = argparse.ArgumentParser(description='Check global names exported from a library') parser.add_argument('library', help='The static library to examine') args = parser.parse_args() if not os.path.isfile(args.library): print('{}: error: {} does not exist'.format(PROG, args.library)) sys.exit(1) if os.name == 'posix': status = check_library(args.library) sys.exit(status) else: print('Passing test since not on Posix') sys.exit(0) if __name__ == '__main__': main()