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 declar classes and | |
|
kapishnikov
2016/12/01 20:34:17
declar -> declare
pauljensen
2016/12/02 18:24:14
Done.
| |
| 24 # methods. The first group returned by a match is the class or method name. | |
| 25 CLASS_RE = re.compile(r'.*class ([^ ]*) .*{') | |
|
kapishnikov
2016/12/01 20:34:16
I think '\' is missing before '{' since we want to
pauljensen
2016/12/02 18:24:14
Done.
| |
| 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 'org.chromium.net.urlconnection.CronetFixedModeOutputStream$UploadDataProviderI' | |
| 36 'mpl/read -> org/chromium/net/UploadDataSink/onReadSucceeded', | |
| 37 'org.chromium.net.urlconnection.CronetFixedModeOutputStream$UploadDataProviderI' | |
| 38 'mpl/rewind -> org/chromium/net/UploadDataSink/onRewindError', | |
| 39 'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect ->' | |
| 40 ' org/chromium/net/UrlRequest/cancel', | |
| 41 'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect ->' | |
| 42 ' org/chromium/net/UrlResponseInfo/getHttpStatusText', | |
| 43 'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect ->' | |
| 44 ' org/chromium/net/UrlResponseInfo/getHttpStatusCode', | |
| 45 'org.chromium.net.urlconnection.CronetHttpURLConnection/getHeaderField ->' | |
| 46 ' org/chromium/net/UrlResponseInfo/getHttpStatusCode', | |
| 47 'org.chromium.net.urlconnection.CronetHttpURLConnection/getErrorStream ->' | |
| 48 ' org/chromium/net/UrlResponseInfo/getHttpStatusCode', | |
| 49 'org.chromium.net.urlconnection.CronetHttpURLConnection/setConnectTimeout ->' | |
| 50 ' org/chromium/net/UrlRequest/read', | |
| 51 'org.chromium.net.urlconnection.CronetHttpURLConnection$CronetUrlRequestCallbac' | |
| 52 'k/onRedirectReceived -> org/chromium/net/UrlRequest/followRedirect', | |
| 53 'org.chromium.net.urlconnection.CronetHttpURLConnection$CronetUrlRequestCallbac' | |
| 54 'k/onRedirectReceived -> org/chromium/net/UrlRequest/cancel', | |
| 55 'org.chromium.net.urlconnection.CronetChunkedOutputStream$UploadDataProviderImp' | |
| 56 'l/read -> org/chromium/net/UploadDataSink/onReadSucceeded', | |
| 57 'org.chromium.net.urlconnection.CronetChunkedOutputStream$UploadDataProviderImp' | |
| 58 'l/rewind -> org/chromium/net/UploadDataSink/onRewindError', | |
| 59 'org.chromium.net.urlconnection.CronetBufferedOutputStream$UploadDataProviderIm' | |
| 60 'pl/read -> org/chromium/net/UploadDataSink/onReadSucceeded', | |
| 61 'org.chromium.net.urlconnection.CronetBufferedOutputStream$UploadDataProviderIm' | |
| 62 'pl/rewind -> org/chromium/net/UploadDataSink/onRewindSucceeded', | |
| 63 ] | |
| 64 | |
| 65 | |
| 66 def find_api_calls(dump, api_classes, bad_calls): | |
| 67 # Given a dump of an implementation class, find calls through API classes. | |
| 68 # |dump| is the output of "javap -c" on the implementation class files. | |
| 69 # |api_classes| is the list of classes comprising the API. | |
| 70 # |bad_calls| is the list of calls through API classes. This list is built up | |
| 71 # by this function. | |
| 72 | |
| 73 for line in dump: | |
| 74 if CLASS_RE.match(line): | |
| 75 caller_class = CLASS_RE.match(line).group(1) | |
| 76 if METHOD_RE.match(line): | |
| 77 caller_method = METHOD_RE.match(line).group(1) | |
| 78 if line[8:16] == ': invoke': | |
|
kapishnikov
2016/12/01 20:34:17
This looks a little fragile. Can the output format
pauljensen
2016/12/02 18:24:14
I doubt the format will ever change; for example "
kapishnikov
2016/12/28 21:54:41
Acknowledged.
| |
| 79 callee = line[54:].split(':')[0] | |
|
kapishnikov
2016/12/01 20:34:16
1. Can we add an assertion that |caller_class| and
pauljensen
2016/12/02 18:24:14
Done.
| |
| 80 callee_class = callee.split('.')[0] | |
| 81 if callee_class in api_classes: | |
| 82 callee_method = callee.split('.')[1] | |
| 83 # Ignore default constructor calls | |
| 84 if callee_method == '"<init>"': | |
|
kapishnikov
2016/12/01 20:34:17
Should we also track the usage of constructors, i.
pauljensen
2016/12/02 18:24:14
We construct plenty of API classes from the impl.
kapishnikov
2016/12/28 21:54:41
Acknowledged.
| |
| 85 continue | |
| 86 # Ignore VersionSafe calls | |
| 87 if 'VersionSafeCallbacks' in caller_class: | |
| 88 continue | |
| 89 bad_call = '%s/%s -> %s/%s' % (caller_class, caller_method, | |
| 90 callee_class, callee_method) | |
| 91 if bad_call in ALLOWED_EXCEPTIONS: | |
| 92 continue | |
| 93 bad_calls += [bad_call] | |
| 94 | |
| 95 | |
| 96 def main(args): | |
| 97 # Returns True if no calls through API classes in implementation. | |
| 98 | |
| 99 parser = argparse.ArgumentParser( | |
| 100 description='Check modules do not contain ARM Neon instructions.') | |
| 101 parser.add_argument('--api_jar', | |
| 102 help='Path to API jar (i.e. cronet_api.jar)', | |
| 103 required=True, | |
| 104 metavar='path/to/cronet_api.jar') | |
| 105 parser.add_argument('--impl_jar', | |
| 106 help='Path to implementation jar ' | |
| 107 '(i.e. cronet_impl_native_java.jar)', | |
| 108 required=True, | |
| 109 metavar='path/to/cronet_impl_native_java.jar', | |
| 110 action='append') | |
| 111 parser.add_argument('--stamp', help='Path to touch on success.') | |
| 112 opts = parser.parse_args(args) | |
| 113 | |
| 114 temp_dir = tempfile.mkdtemp() | |
| 115 | |
| 116 # Extract API class files from jar | |
| 117 jar_cmd = ['jar', 'xf', os.path.abspath(opts.api_jar)] | |
| 118 build_utils.CheckOutput(jar_cmd, cwd=temp_dir) | |
| 119 shutil.rmtree(os.path.join(temp_dir, 'META-INF')) | |
| 120 | |
| 121 # Collect names of API classes | |
| 122 api_classes = [] | |
| 123 for root, _, filenames in os.walk(temp_dir): | |
|
kapishnikov
2016/12/01 20:34:17
Should |root| be renamed to |dirpath|? I guess |te
pauljensen
2016/12/02 18:24:14
Done.
| |
| 124 if not filenames: | |
| 125 continue | |
| 126 package = root[len(temp_dir + '/'):] | |
| 127 for filename in filenames: | |
| 128 if filename.endswith('.class'): | |
| 129 classname = filename[:-len('.class')] | |
| 130 api_classes += [package + '/' + classname] | |
| 131 | |
| 132 shutil.rmtree(temp_dir) | |
| 133 temp_dir = tempfile.mkdtemp() | |
| 134 | |
| 135 # Extract impl class files from jars | |
| 136 for impl_jar in opts.impl_jar: | |
| 137 jar_cmd = ['jar', 'xf', os.path.abspath(impl_jar)] | |
| 138 build_utils.CheckOutput(jar_cmd, cwd=temp_dir) | |
| 139 shutil.rmtree(os.path.join(temp_dir, 'META-INF')) | |
| 140 | |
| 141 # Process classes | |
| 142 api_calls = [] | |
|
kapishnikov
2016/12/01 20:34:16
Should we rename it to |bad_api_calls|?
pauljensen
2016/12/02 18:24:14
Done.
| |
| 143 for root, _, filenames in os.walk(temp_dir): | |
| 144 if not filenames: | |
| 145 continue | |
| 146 # Dump classes | |
| 147 dump_file = os.path.join(temp_dir, 'dump.txt') | |
| 148 if os.system('javap -c %s > %s' % ( | |
| 149 ' '.join(os.path.join(root, f) for f in filenames).replace('$', '\\$'), | |
| 150 dump_file)): | |
| 151 print 'ERROR: javap failed on ' + filenames | |
|
kapishnikov
2016/12/01 20:34:16
I think it is not allowed to concatenate string wi
pauljensen
2016/12/02 18:24:14
Done.
| |
| 152 return False | |
| 153 # Process class dump | |
| 154 with open(dump_file, 'r') as dump: | |
| 155 find_api_calls(dump, api_classes, api_calls) | |
| 156 | |
| 157 shutil.rmtree(temp_dir) | |
| 158 | |
| 159 if api_calls: | |
| 160 print 'ERROR: Found the following calls from implementation classes through' | |
| 161 print ' API classes. These could fail if older API is used that' | |
| 162 print ' does not contain newer methods. Please call through a' | |
| 163 print ' wrapper class from VersionSafeCallbacks.' | |
| 164 print '\n'.join(api_calls) | |
| 165 | |
| 166 if not api_calls and opts.stamp: | |
| 167 build_utils.Touch(opts.stamp) | |
| 168 return not api_calls | |
| 169 | |
| 170 | |
| 171 if __name__ == '__main__': | |
| 172 sys.exit(0 if main(sys.argv[1:]) != 0 else -1) | |
| 173 | |
| OLD | NEW |