1fd4e5da5Sopenharmony_ci#!/usr/bin/env python
2fd4e5da5Sopenharmony_ci# Copyright (c) 2017 Google Inc.
3fd4e5da5Sopenharmony_ci
4fd4e5da5Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
5fd4e5da5Sopenharmony_ci# you may not use this file except in compliance with the License.
6fd4e5da5Sopenharmony_ci# You may obtain a copy of the License at
7fd4e5da5Sopenharmony_ci#
8fd4e5da5Sopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
9fd4e5da5Sopenharmony_ci#
10fd4e5da5Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
11fd4e5da5Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
12fd4e5da5Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13fd4e5da5Sopenharmony_ci# See the License for the specific language governing permissions and
14fd4e5da5Sopenharmony_ci# limitations under the License.
15fd4e5da5Sopenharmony_ci"""Ensures that all externally visible functions in the library have an appropriate name
16fd4e5da5Sopenharmony_ci
17fd4e5da5Sopenharmony_ciAppropriate function names are:
18fd4e5da5Sopenharmony_ci  - names starting with spv,
19fd4e5da5Sopenharmony_ci  - anything in a namespace,
20fd4e5da5Sopenharmony_ci  - functions added by the protobuf compiler,
21fd4e5da5Sopenharmony_ci  - and weak definitions of new and delete."""
22fd4e5da5Sopenharmony_ci
23fd4e5da5Sopenharmony_ciimport os.path
24fd4e5da5Sopenharmony_ciimport re
25fd4e5da5Sopenharmony_ciimport subprocess
26fd4e5da5Sopenharmony_ciimport sys
27fd4e5da5Sopenharmony_ci
28fd4e5da5Sopenharmony_ci
29fd4e5da5Sopenharmony_ciPROG = 'check_symbol_exports'
30fd4e5da5Sopenharmony_ci
31fd4e5da5Sopenharmony_ci
32fd4e5da5Sopenharmony_cidef command_output(cmd, directory):
33fd4e5da5Sopenharmony_ci    """Runs a command in a directory and returns its standard output stream.
34fd4e5da5Sopenharmony_ci
35fd4e5da5Sopenharmony_ci    Captures the standard error stream.
36fd4e5da5Sopenharmony_ci
37fd4e5da5Sopenharmony_ci    Raises a RuntimeError if the command fails to launch or otherwise fails.
38fd4e5da5Sopenharmony_ci    """
39fd4e5da5Sopenharmony_ci    p = subprocess.Popen(cmd,
40fd4e5da5Sopenharmony_ci                         cwd=directory,
41fd4e5da5Sopenharmony_ci                         stdout=subprocess.PIPE,
42fd4e5da5Sopenharmony_ci                         stderr=subprocess.PIPE,
43fd4e5da5Sopenharmony_ci                         universal_newlines=True)
44fd4e5da5Sopenharmony_ci    (stdout, _) = p.communicate()
45fd4e5da5Sopenharmony_ci    if p.returncode != 0:
46fd4e5da5Sopenharmony_ci        raise RuntimeError('Failed to run %s in %s' % (cmd, directory))
47fd4e5da5Sopenharmony_ci    return stdout
48fd4e5da5Sopenharmony_ci
49fd4e5da5Sopenharmony_ci
50fd4e5da5Sopenharmony_cidef check_library(library):
51fd4e5da5Sopenharmony_ci    """Scans the given library file for global exports.  If all such
52fd4e5da5Sopenharmony_ci    exports are namespaced or begin with spv (in either C or C++ styles)
53fd4e5da5Sopenharmony_ci    then return 0.  Otherwise emit a message and return 1."""
54fd4e5da5Sopenharmony_ci
55fd4e5da5Sopenharmony_ci    # The pattern for an externally visible symbol record
56fd4e5da5Sopenharmony_ci    symbol_pattern = re.compile(r'^[0-aA-Fa-f]+ +([wg]) *F \.text.*[0-9A-Fa-f]+ +(.*)')
57fd4e5da5Sopenharmony_ci
58fd4e5da5Sopenharmony_ci    # Ok patterns are as follows, assuming Itanium name mangling:
59fd4e5da5Sopenharmony_ci    #   spv[A-Z]          :  extern "C" symbol starting with spv
60fd4e5da5Sopenharmony_ci    #   _ZN               :  something in a namespace
61fd4e5da5Sopenharmony_ci    #   _ZSt              :  something in the standard namespace
62fd4e5da5Sopenharmony_ci    #   _ZZN              :  something in a local scope and namespace
63fd4e5da5Sopenharmony_ci    #   _Z[0-9]+spv[A-Z_] :  C++ symbol starting with spv[A-Z_]
64fd4e5da5Sopenharmony_ci    symbol_ok_pattern = re.compile(r'^(spv[A-Z]|_ZN|_ZSt|_ZZN|_Z[0-9]+spv[A-Z_])')
65fd4e5da5Sopenharmony_ci
66fd4e5da5Sopenharmony_ci    # In addition, the following pattern allowlists global functions that are added
67fd4e5da5Sopenharmony_ci    # by the protobuf compiler:
68fd4e5da5Sopenharmony_ci    #   - AddDescriptors_spvtoolsfuzz_2eproto()
69fd4e5da5Sopenharmony_ci    #   - InitDefaults_spvtoolsfuzz_2eproto()
70fd4e5da5Sopenharmony_ci    symbol_allowlist_pattern = re.compile(r'_Z[0-9]+.*spvtoolsfuzz_2eproto.*')
71fd4e5da5Sopenharmony_ci
72fd4e5da5Sopenharmony_ci    symbol_is_new_or_delete = re.compile(r'^(_Zna|_Znw|_Zdl|_Zda)')
73fd4e5da5Sopenharmony_ci    # Compilaion for Arm has various thunks for constructors, destructors, vtables.
74fd4e5da5Sopenharmony_ci    # They are weak.
75fd4e5da5Sopenharmony_ci    symbol_is_thunk = re.compile(r'^_ZT')
76fd4e5da5Sopenharmony_ci
77fd4e5da5Sopenharmony_ci    # This occurs in NDK builds.
78fd4e5da5Sopenharmony_ci    symbol_is_hidden = re.compile(r'^\.hidden ')
79fd4e5da5Sopenharmony_ci
80fd4e5da5Sopenharmony_ci    seen = set()
81fd4e5da5Sopenharmony_ci    result = 0
82fd4e5da5Sopenharmony_ci    for line in command_output(['objdump', '-t', library], '.').split('\n'):
83fd4e5da5Sopenharmony_ci        match = symbol_pattern.search(line)
84fd4e5da5Sopenharmony_ci        if match:
85fd4e5da5Sopenharmony_ci            linkage = match.group(1)
86fd4e5da5Sopenharmony_ci            symbol = match.group(2)
87fd4e5da5Sopenharmony_ci            if symbol not in seen:
88fd4e5da5Sopenharmony_ci                seen.add(symbol)
89fd4e5da5Sopenharmony_ci                #print("look at '{}'".format(symbol))
90fd4e5da5Sopenharmony_ci                if not (symbol_is_new_or_delete.match(symbol) and linkage == 'w'):
91fd4e5da5Sopenharmony_ci                    if not (symbol_is_thunk.match(symbol) and linkage == 'w'):
92fd4e5da5Sopenharmony_ci                        if not (symbol_allowlist_pattern.match(symbol) or
93fd4e5da5Sopenharmony_ci                                symbol_ok_pattern.match(symbol) or
94fd4e5da5Sopenharmony_ci                                symbol_is_hidden.match(symbol)):
95fd4e5da5Sopenharmony_ci                            print('{}: error: Unescaped exported symbol: {}'.format(PROG, symbol))
96fd4e5da5Sopenharmony_ci                            result = 1
97fd4e5da5Sopenharmony_ci    return result
98fd4e5da5Sopenharmony_ci
99fd4e5da5Sopenharmony_ci
100fd4e5da5Sopenharmony_cidef main():
101fd4e5da5Sopenharmony_ci    import argparse
102fd4e5da5Sopenharmony_ci    parser = argparse.ArgumentParser(description='Check global names exported from a library')
103fd4e5da5Sopenharmony_ci    parser.add_argument('library', help='The static library to examine')
104fd4e5da5Sopenharmony_ci    args = parser.parse_args()
105fd4e5da5Sopenharmony_ci
106fd4e5da5Sopenharmony_ci    if not os.path.isfile(args.library):
107fd4e5da5Sopenharmony_ci        print('{}: error: {} does not exist'.format(PROG, args.library))
108fd4e5da5Sopenharmony_ci        sys.exit(1)
109fd4e5da5Sopenharmony_ci
110fd4e5da5Sopenharmony_ci    if os.name == 'posix':
111fd4e5da5Sopenharmony_ci        status = check_library(args.library)
112fd4e5da5Sopenharmony_ci        sys.exit(status)
113fd4e5da5Sopenharmony_ci    else:
114fd4e5da5Sopenharmony_ci        print('Passing test since not on Posix')
115fd4e5da5Sopenharmony_ci        sys.exit(0)
116fd4e5da5Sopenharmony_ci
117fd4e5da5Sopenharmony_ci
118fd4e5da5Sopenharmony_ciif __name__ == '__main__':
119fd4e5da5Sopenharmony_ci    main()
120