SPIRV-Tools/utils/check_symbol_exports.py
Steven Perron 8ec9f456e6
Fix export symbol test. (#4254)
* Fix export symbol test.

The symbol export test does not check weak symbols, even though they are
possibly exported.  It also does not allow functions in the standard
namespace even though it allows symbols on other namespaces.

I've modified the check to look at the name of weakly defined function,
and I've allowed functions in the standard namespace, functions in a local scope, and
weak definitions of new and delete operators.

Fixes #4250
2021-04-29 14:27:16 -04:00

111 lines
4.0 KiB
Python
Executable File

#!/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)')
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_allowlist_pattern.match(symbol) or symbol_ok_pattern.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()