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