Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python | |
| 2 # Copyright 2016 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """api_static_checks.py - Check Cronet implementation does not call through | |
| 7 API classes. | |
| 8 """ | |
| 9 | |
| 10 import argparse | |
| 11 import os | |
| 12 import re | |
| 13 import shutil | |
| 14 import sys | |
| 15 import tempfile | |
| 16 | |
| 17 REPOSITORY_ROOT = os.path.abspath(os.path.join( | |
| 18 os.path.dirname(__file__), '..', '..', '..')) | |
| 19 | |
| 20 sys.path.append(os.path.join(REPOSITORY_ROOT, 'build/android/gyp/util')) | |
| 21 import build_utils | |
| 22 | |
| 23 # These regular expressions catch the beginning of lines that declare classes | |
| 24 # and methods. The first group returned by a match is the class or method name. | |
| 25 CLASS_RE = re.compile(r'.*class ([^ ]*) .*\{') | |
| 26 METHOD_RE = re.compile(r'.* ([^ ]*)\(.*\);') | |
| 27 | |
| 28 # Allowed exceptions. Adding anything to this list is dangerous and should be | |
| 29 # avoided if possible. For now these exceptions are for APIs that existed in | |
| 30 # the first version of Cronet and will be supported forever. | |
| 31 # TODO(pauljensen): Remove these. | |
| 32 ALLOWED_EXCEPTIONS = [ | |
| 33 'org.chromium.net.impl.CronetEngineBuilderImpl/build ->' | |
| 34 ' org/chromium/net/ExperimentalCronetEngine/getVersionString:' | |
| 35 '()Ljava/lang/String;', | |
| 36 'org.chromium.net.urlconnection.CronetFixedModeOutputStream$UploadDataProviderI' | |
| 37 'mpl/read -> org/chromium/net/UploadDataSink/onReadSucceeded:(Z)V', | |
| 38 'org.chromium.net.urlconnection.CronetFixedModeOutputStream$UploadDataProviderI' | |
| 39 'mpl/rewind -> org/chromium/net/UploadDataSink/onRewindError:' | |
| 40 '(Ljava/lang/Exception;)V', | |
| 41 'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect ->' | |
| 42 ' org/chromium/net/UrlRequest/cancel:()V', | |
| 43 'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect ->' | |
| 44 ' org/chromium/net/UrlResponseInfo/getHttpStatusText:()Ljava/lang/String;', | |
| 45 'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect ->' | |
| 46 ' org/chromium/net/UrlResponseInfo/getHttpStatusCode:()I', | |
| 47 'org.chromium.net.urlconnection.CronetHttpURLConnection/getHeaderField ->' | |
| 48 ' org/chromium/net/UrlResponseInfo/getHttpStatusCode:()I', | |
| 49 'org.chromium.net.urlconnection.CronetHttpURLConnection/getErrorStream ->' | |
| 50 ' org/chromium/net/UrlResponseInfo/getHttpStatusCode:()I', | |
| 51 'org.chromium.net.urlconnection.CronetHttpURLConnection/setConnectTimeout ->' | |
| 52 ' org/chromium/net/UrlRequest/read:(Ljava/nio/ByteBuffer;)V', | |
| 53 'org.chromium.net.urlconnection.CronetHttpURLConnection$CronetUrlRequestCallbac' | |
| 54 'k/onRedirectReceived -> org/chromium/net/UrlRequest/followRedirect:()V', | |
| 55 'org.chromium.net.urlconnection.CronetHttpURLConnection$CronetUrlRequestCallbac' | |
| 56 'k/onRedirectReceived -> org/chromium/net/UrlRequest/cancel:()V', | |
| 57 'org.chromium.net.urlconnection.CronetChunkedOutputStream$UploadDataProviderImp' | |
| 58 'l/read -> org/chromium/net/UploadDataSink/onReadSucceeded:(Z)V', | |
| 59 'org.chromium.net.urlconnection.CronetChunkedOutputStream$UploadDataProviderImp' | |
| 60 'l/rewind -> org/chromium/net/UploadDataSink/onRewindError:' | |
| 61 '(Ljava/lang/Exception;)V', | |
| 62 'org.chromium.net.urlconnection.CronetBufferedOutputStream$UploadDataProviderIm' | |
| 63 'pl/read -> org/chromium/net/UploadDataSink/onReadSucceeded:(Z)V', | |
| 64 'org.chromium.net.urlconnection.CronetBufferedOutputStream$UploadDataProviderIm' | |
| 65 'pl/rewind -> org/chromium/net/UploadDataSink/onRewindSucceeded:()V', | |
| 66 ] | |
| 67 | |
| 68 | |
| 69 def find_api_calls(dump, api_classes, bad_calls): | |
| 70 # Given a dump of an implementation class, find calls through API classes. | |
| 71 # |dump| is the output of "javap -c" on the implementation class files. | |
| 72 # |api_classes| is the list of classes comprising the API. | |
| 73 # |bad_calls| is the list of calls through API classes. This list is built up | |
| 74 # by this function. | |
| 75 | |
| 76 for line in dump: | |
| 77 if CLASS_RE.match(line): | |
| 78 caller_class = CLASS_RE.match(line).group(1) | |
| 79 if METHOD_RE.match(line): | |
| 80 caller_method = METHOD_RE.match(line).group(1) | |
| 81 if line[8:16] == ': invoke': | |
| 82 callee = line.split(' // ')[1].split('Method ')[1].split('\n')[0] | |
| 83 callee_class = callee.split('.')[0] | |
| 84 assert callee_class | |
| 85 if callee_class in api_classes: | |
| 86 callee_method = callee.split('.')[1] | |
| 87 assert callee_method | |
| 88 # Ignore constructor calls for now as every implementation class | |
| 89 # that extends an API class will call them. | |
| 90 # TODO(pauljensen): Look into enforcing restricting constructor calls. | |
| 91 # https://crbug.com/674975 | |
| 92 if callee_method.startswith('"<init>"'): | |
| 93 continue | |
| 94 # Ignore VersionSafe calls | |
| 95 if 'VersionSafeCallbacks' in caller_class: | |
| 96 continue | |
| 97 bad_call = '%s/%s -> %s/%s' % (caller_class, caller_method, | |
| 98 callee_class, callee_method) | |
| 99 if bad_call in ALLOWED_EXCEPTIONS: | |
| 100 continue | |
| 101 bad_calls += [bad_call] | |
| 102 | |
| 103 | |
| 104 def main(args): | |
| 105 # Returns True if no calls through API classes in implementation. | |
| 106 | |
| 107 parser = argparse.ArgumentParser( | |
| 108 description='Check modules do not contain ARM Neon instructions.') | |
| 109 parser.add_argument('--api_jar', | |
| 110 help='Path to API jar (i.e. cronet_api.jar)', | |
| 111 required=True, | |
| 112 metavar='path/to/cronet_api.jar') | |
| 113 parser.add_argument('--impl_jar', | |
| 114 help='Path to implementation jar ' | |
| 115 '(i.e. cronet_impl_native_java.jar)', | |
| 116 required=True, | |
| 117 metavar='path/to/cronet_impl_native_java.jar', | |
| 118 action='append') | |
| 119 parser.add_argument('--stamp', help='Path to touch on success.') | |
| 120 opts = parser.parse_args(args) | |
| 121 | |
| 122 temp_dir = tempfile.mkdtemp() | |
| 123 | |
| 124 # Extract API class files from jar | |
| 125 jar_cmd = ['jar', 'xf', os.path.abspath(opts.api_jar)] | |
| 126 build_utils.CheckOutput(jar_cmd, cwd=temp_dir) | |
| 127 shutil.rmtree(os.path.join(temp_dir, 'META-INF')) | |
| 128 | |
| 129 # Collect names of API classes | |
| 130 api_classes = [] | |
| 131 for dirpath, _, filenames in os.walk(temp_dir): | |
| 132 if not filenames: | |
| 133 continue | |
| 134 package = dirpath[len(temp_dir + '/'):] | |
| 135 if package: | |
| 136 package += '/' | |
|
kapishnikov
2016/12/28 21:54:41
Same comment as in 2544043002
pauljensen
2017/01/03 14:56:49
Done.
| |
| 137 for filename in filenames: | |
| 138 if filename.endswith('.class'): | |
| 139 classname = filename[:-len('.class')] | |
| 140 api_classes += [package + classname] | |
|
kapishnikov
2016/12/28 21:54:41
Same comment as in 2544043002
pauljensen
2017/01/03 14:56:49
Done, I had to wrap it in os.path.normpath() becau
| |
| 141 | |
| 142 shutil.rmtree(temp_dir) | |
| 143 temp_dir = tempfile.mkdtemp() | |
| 144 | |
| 145 # Extract impl class files from jars | |
| 146 for impl_jar in opts.impl_jar: | |
| 147 jar_cmd = ['jar', 'xf', os.path.abspath(impl_jar)] | |
| 148 build_utils.CheckOutput(jar_cmd, cwd=temp_dir) | |
| 149 shutil.rmtree(os.path.join(temp_dir, 'META-INF')) | |
| 150 | |
| 151 # Process classes | |
| 152 bad_api_calls = [] | |
| 153 for dirpath, _, filenames in os.walk(temp_dir): | |
| 154 if not filenames: | |
| 155 continue | |
| 156 # Dump classes | |
| 157 dump_file = os.path.join(temp_dir, 'dump.txt') | |
| 158 if os.system('javap -c %s > %s' % ( | |
| 159 ' '.join(os.path.join(dirpath, f) for f in filenames).replace( | |
| 160 '$', '\\$'), | |
| 161 dump_file)): | |
| 162 print 'ERROR: javap failed on ' + ' '.join(filenames) | |
| 163 return False | |
| 164 # Process class dump | |
| 165 with open(dump_file, 'r') as dump: | |
| 166 find_api_calls(dump, api_classes, bad_api_calls) | |
| 167 | |
| 168 shutil.rmtree(temp_dir) | |
| 169 | |
| 170 if bad_api_calls: | |
| 171 print 'ERROR: Found the following calls from implementation classes through' | |
| 172 print ' API classes. These could fail if older API is used that' | |
| 173 print ' does not contain newer methods. Please call through a' | |
| 174 print ' wrapper class from VersionSafeCallbacks.' | |
| 175 print '\n'.join(bad_api_calls) | |
| 176 | |
| 177 if not bad_api_calls and opts.stamp: | |
| 178 build_utils.Touch(opts.stamp) | |
| 179 return not bad_api_calls | |
| 180 | |
| 181 | |
| 182 if __name__ == '__main__': | |
| 183 sys.exit(0 if main(sys.argv[1:]) else -1) | |
| OLD | NEW |