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