| 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 # getMessage() is an java.lang.Exception member, and so cannot be removed. |
| 67 'org.chromium.net.impl.NetworkExceptionImpl/getMessage -> ' |
| 68 'org/chromium/net/NetworkException/getMessage:()Ljava/lang/String;', |
| 69 ] |
| 70 |
| 71 |
| 72 def find_api_calls(dump, api_classes, bad_calls): |
| 73 # Given a dump of an implementation class, find calls through API classes. |
| 74 # |dump| is the output of "javap -c" on the implementation class files. |
| 75 # |api_classes| is the list of classes comprising the API. |
| 76 # |bad_calls| is the list of calls through API classes. This list is built up |
| 77 # by this function. |
| 78 |
| 79 for line in dump: |
| 80 if CLASS_RE.match(line): |
| 81 caller_class = CLASS_RE.match(line).group(1) |
| 82 if METHOD_RE.match(line): |
| 83 caller_method = METHOD_RE.match(line).group(1) |
| 84 if line[8:16] == ': invoke': |
| 85 callee = line.split(' // ')[1].split('Method ')[1].split('\n')[0] |
| 86 callee_class = callee.split('.')[0] |
| 87 assert callee_class |
| 88 if callee_class in api_classes: |
| 89 callee_method = callee.split('.')[1] |
| 90 assert callee_method |
| 91 # Ignore constructor calls for now as every implementation class |
| 92 # that extends an API class will call them. |
| 93 # TODO(pauljensen): Look into enforcing restricting constructor calls. |
| 94 # https://crbug.com/674975 |
| 95 if callee_method.startswith('"<init>"'): |
| 96 continue |
| 97 # Ignore VersionSafe calls |
| 98 if 'VersionSafeCallbacks' in caller_class: |
| 99 continue |
| 100 bad_call = '%s/%s -> %s/%s' % (caller_class, caller_method, |
| 101 callee_class, callee_method) |
| 102 if bad_call in ALLOWED_EXCEPTIONS: |
| 103 continue |
| 104 bad_calls += [bad_call] |
| 105 |
| 106 |
| 107 def main(args): |
| 108 # Returns True if no calls through API classes in implementation. |
| 109 |
| 110 parser = argparse.ArgumentParser( |
| 111 description='Check modules do not contain ARM Neon instructions.') |
| 112 parser.add_argument('--api_jar', |
| 113 help='Path to API jar (i.e. cronet_api.jar)', |
| 114 required=True, |
| 115 metavar='path/to/cronet_api.jar') |
| 116 parser.add_argument('--impl_jar', |
| 117 help='Path to implementation jar ' |
| 118 '(i.e. cronet_impl_native_java.jar)', |
| 119 required=True, |
| 120 metavar='path/to/cronet_impl_native_java.jar', |
| 121 action='append') |
| 122 parser.add_argument('--stamp', help='Path to touch on success.') |
| 123 opts = parser.parse_args(args) |
| 124 |
| 125 temp_dir = tempfile.mkdtemp() |
| 126 |
| 127 # Extract API class files from jar |
| 128 jar_cmd = ['jar', 'xf', os.path.abspath(opts.api_jar)] |
| 129 build_utils.CheckOutput(jar_cmd, cwd=temp_dir) |
| 130 shutil.rmtree(os.path.join(temp_dir, 'META-INF')) |
| 131 |
| 132 # Collect names of API classes |
| 133 api_classes = [] |
| 134 for dirpath, _, filenames in os.walk(temp_dir): |
| 135 if not filenames: |
| 136 continue |
| 137 package = os.path.relpath(dirpath, temp_dir) |
| 138 for filename in filenames: |
| 139 if filename.endswith('.class'): |
| 140 classname = filename[:-len('.class')] |
| 141 api_classes += [os.path.normpath(os.path.join(package, classname))] |
| 142 |
| 143 shutil.rmtree(temp_dir) |
| 144 temp_dir = tempfile.mkdtemp() |
| 145 |
| 146 # Extract impl class files from jars |
| 147 for impl_jar in opts.impl_jar: |
| 148 jar_cmd = ['jar', 'xf', os.path.abspath(impl_jar)] |
| 149 build_utils.CheckOutput(jar_cmd, cwd=temp_dir) |
| 150 shutil.rmtree(os.path.join(temp_dir, 'META-INF')) |
| 151 |
| 152 # Process classes |
| 153 bad_api_calls = [] |
| 154 for dirpath, _, filenames in os.walk(temp_dir): |
| 155 if not filenames: |
| 156 continue |
| 157 # Dump classes |
| 158 dump_file = os.path.join(temp_dir, 'dump.txt') |
| 159 if os.system('javap -c %s > %s' % ( |
| 160 ' '.join(os.path.join(dirpath, f) for f in filenames).replace( |
| 161 '$', '\\$'), |
| 162 dump_file)): |
| 163 print 'ERROR: javap failed on ' + ' '.join(filenames) |
| 164 return False |
| 165 # Process class dump |
| 166 with open(dump_file, 'r') as dump: |
| 167 find_api_calls(dump, api_classes, bad_api_calls) |
| 168 |
| 169 shutil.rmtree(temp_dir) |
| 170 |
| 171 if bad_api_calls: |
| 172 print 'ERROR: Found the following calls from implementation classes through' |
| 173 print ' API classes. These could fail if older API is used that' |
| 174 print ' does not contain newer methods. Please call through a' |
| 175 print ' wrapper class from VersionSafeCallbacks.' |
| 176 print '\n'.join(bad_api_calls) |
| 177 |
| 178 if not bad_api_calls and opts.stamp: |
| 179 build_utils.Touch(opts.stamp) |
| 180 return not bad_api_calls |
| 181 |
| 182 |
| 183 if __name__ == '__main__': |
| 184 sys.exit(0 if main(sys.argv[1:]) else -1) |
| OLD | NEW |