1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
#!/usr/bin/env python
# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""api_static_checks.py - Enforce Cronet API requirements."""
import argparse
import os
import re
import shutil
import sys
import tempfile
REPOSITORY_ROOT = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir))
sys.path.insert(0, os.path.join(REPOSITORY_ROOT, 'build/android/gyp'))
from util import build_utils # pylint: disable=wrong-import-position
sys.path.insert(0, os.path.join(REPOSITORY_ROOT, 'components'))
from cronet.tools import update_api # pylint: disable=wrong-import-position
# These regular expressions catch the beginning of lines that declare classes
# and methods. The first group returned by a match is the class or method name.
from cronet.tools.update_api import CLASS_RE # pylint: disable=wrong-import-position
METHOD_RE = re.compile(r".* ([^ ]*)\(.*\)( .+)?;")
# Allowed exceptions. Adding anything to this list is dangerous and should be
# avoided if possible. For now these exceptions are for APIs that existed in
# the first version of Cronet and will be supported forever.
# TODO(pauljensen): Remove these.
ALLOWED_EXCEPTIONS = [
'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect -> org/chromium/net/UrlRequest/cancel:()V',
'org.chromium.net.urlconnection.CronetHttpURLConnection/getResponseMessage -> org/chromium/net/UrlResponseInfo/getHttpStatusText:()Ljava/lang/String;',
'org.chromium.net.urlconnection.CronetHttpURLConnection/getResponseCode -> org/chromium/net/UrlResponseInfo/getHttpStatusCode:()I',
'org.chromium.net.urlconnection.CronetHttpURLConnection/getInputStream -> org/chromium/net/UrlResponseInfo/getHttpStatusCode:()I',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/CronetEngine/newUrlRequestBuilder:(Ljava/lang/String;Lorg/chromium/net/UrlRequest$Callback;Ljava/util/concurrent/Executor;)Lorg/chromium/net/UrlRequest$Builder;',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/ExperimentalUrlRequest$Builder/setUploadDataProvider:(Lorg/chromium/net/UploadDataProvider;Ljava/util/concurrent/Executor;)Lorg/chromium/net/ExperimentalUrlRequest$Builder;',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/UploadDataProvider/getLength:()J',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/ExperimentalUrlRequest$Builder/addHeader:(Ljava/lang/String;Ljava/lang/String;)Lorg/chromium/net/ExperimentalUrlRequest$Builder;',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/ExperimentalUrlRequest$Builder/disableCache:()Lorg/chromium/net/ExperimentalUrlRequest$Builder;',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/ExperimentalUrlRequest$Builder/setHttpMethod:(Ljava/lang/String;)Lorg/chromium/net/ExperimentalUrlRequest$Builder;',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/ExperimentalUrlRequest$Builder/setTrafficStatsTag:(I)Lorg/chromium/net/ExperimentalUrlRequest$Builder;',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/ExperimentalUrlRequest$Builder/setTrafficStatsUid:(I)Lorg/chromium/net/ExperimentalUrlRequest$Builder;',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/ExperimentalUrlRequest$Builder/build:()Lorg/chromium/net/ExperimentalUrlRequest;',
'org.chromium.net.urlconnection.CronetHttpURLConnection/startRequest -> org/chromium/net/UrlRequest/start:()V',
'org.chromium.net.urlconnection.CronetHttpURLConnection/getErrorStream -> org/chromium/net/UrlResponseInfo/getHttpStatusCode:()I',
'org.chromium.net.urlconnection.CronetHttpURLConnection/getMoreData -> org/chromium/net/UrlRequest/read:(Ljava/nio/ByteBuffer;)V',
'org.chromium.net.urlconnection.CronetHttpURLConnection/getAllHeadersAsList -> org/chromium/net/UrlResponseInfo/getAllHeadersAsList:()Ljava/util/List;',
'org.chromium.net.urlconnection.CronetChunkedOutputStream$UploadDataProviderImpl/read -> org/chromium/net/UploadDataSink/onReadSucceeded:(Z)V',
'org.chromium.net.urlconnection.CronetChunkedOutputStream$UploadDataProviderImpl/rewind -> org/chromium/net/UploadDataSink/onRewindError:(Ljava/lang/Exception;)V',
'org.chromium.net.urlconnection.CronetFixedModeOutputStream$UploadDataProviderImpl/read -> org/chromium/net/UploadDataSink/onReadSucceeded:(Z)V',
'org.chromium.net.urlconnection.CronetFixedModeOutputStream$UploadDataProviderImpl/rewind -> org/chromium/net/UploadDataSink/onRewindError:(Ljava/lang/Exception;)V',
'org.chromium.net.urlconnection.CronetHttpURLConnection$CronetUrlRequestCallback/onRedirectReceived -> org/chromium/net/UrlRequest/followRedirect:()V',
'org.chromium.net.urlconnection.CronetHttpURLConnection$CronetUrlRequestCallback/onRedirectReceived -> org/chromium/net/UrlRequest/cancel:()V',
'org.chromium.net.urlconnection.CronetHttpURLStreamHandler/openConnection -> org/chromium/net/ExperimentalCronetEngine/openConnection:(Ljava/net/URL;)Ljava/net/URLConnection;',
'org.chromium.net.urlconnection.CronetHttpURLStreamHandler/openConnection -> org/chromium/net/ExperimentalCronetEngine/openConnection:(Ljava/net/URL;Ljava/net/Proxy;)Ljava/net/URLConnection;',
'org.chromium.net.urlconnection.CronetBufferedOutputStream$UploadDataProviderImpl/read -> org/chromium/net/UploadDataSink/onReadSucceeded:(Z)V',
'org.chromium.net.urlconnection.CronetBufferedOutputStream$UploadDataProviderImpl/rewind -> org/chromium/net/UploadDataSink/onRewindSucceeded:()V',
'org.chromium.net.impl.CronetEngineBase/newBidirectionalStreamBuilder -> org/chromium/net/ExperimentalCronetEngine/newBidirectionalStreamBuilder:(Ljava/lang/String;Lorg/chromium/net/BidirectionalStream$Callback;Ljava/util/concurrent/Executor;)Lorg/chromium/net/ExperimentalBidirectionalStream$Builder;',
'org.chromium.net.impl.NetworkExceptionImpl/getMessage -> org/chromium/net/NetworkException/getMessage:()Ljava/lang/String;',
'org.chromium.net.impl.VersionSafeProxyOptions/org.chromium.net.impl.VersionSafeProxyOptions -> org/chromium/net/ProxyOptions/getProxyList:()Ljava/util/List;',
'org.chromium.net.impl.VersionSafeProxyOptions/createProxyCallbackList -> org/chromium/net/ProxyOptions/getProxyList:()Ljava/util/List;',
'org.chromium.net.impl.VersionSafeProxyOptions/createProxyCallbackList -> org/chromium/net/Proxy/getCallback:()Lorg/chromium/net/Proxy$Callback;',
'org.chromium.net.impl.VersionSafeProxyOptions/createProxyOptionsProto -> org/chromium/net/ProxyOptions/getProxyList:()Ljava/util/List;',
'org.chromium.net.impl.VersionSafeProxyOptions/createProxyOptionsProto -> org/chromium/net/Proxy/getHost:()Ljava/lang/String;',
'org.chromium.net.impl.VersionSafeProxyOptions/createProxyOptionsProto -> org/chromium/net/Proxy/getPort:()I',
'org.chromium.net.impl.VersionSafeProxyOptions/createProxyOptionsProto -> org/chromium/net/Proxy/getScheme:()I',
'org.chromium.net.impl.VersionSafeProxyCallback/onBeforeTunnelRequest -> org/chromium/net/Proxy$Callback/onBeforeTunnelRequest:()Ljava/util/List;',
'org.chromium.net.impl.VersionSafeProxyCallback/onTunnelHeadersReceived -> org/chromium/net/Proxy$Callback/onTunnelHeadersReceived:(Ljava/util/List;I)Z',
]
JAR_PATH = os.path.join(build_utils.JAVA_HOME, 'bin', 'jar')
JAVAP_PATH = os.path.join(build_utils.JAVA_HOME, 'bin', 'javap')
def find_api_calls(dump, api_classes, bad_calls):
# Given a dump of an implementation class, find calls through API classes.
# |dump| is the output of "javap -c" on the implementation class files.
# |api_classes| is the list of classes comprising the API.
# |bad_calls| is the list of calls through API classes. This list is built up
# by this function.
for i, line in enumerate(dump):
try:
if CLASS_RE.match(line):
caller_class = CLASS_RE.match(line).group(2)
if METHOD_RE.match(line):
caller_method = METHOD_RE.match(line).group(1)
if line.startswith(': invoke', 8) and not line.startswith('dynamic', 16):
callee = line.split(' // ')[1].split('Method ')[1].split('\n')[0]
callee_class = callee.split('.')[0]
assert callee_class
if callee_class in api_classes:
callee_method = callee.split('.')[1]
assert callee_method
# Ignore constructor calls for now as every implementation class
# that extends an API class will call them.
# TODO(pauljensen): Look into enforcing restricting constructor calls.
# https://crbug.com/674975
if callee_method.startswith('"<init>"'):
continue
# Ignore VersionSafe calls
if 'VersionSafeCallbacks' in caller_class:
continue
bad_call = '%s/%s -> %s/%s' % (caller_class, caller_method,
callee_class, callee_method)
if bad_call in ALLOWED_EXCEPTIONS:
continue
bad_calls += [bad_call]
except Exception:
sys.stderr.write(f'Failed on line {i+1}: {line}')
raise
def check_api_calls(opts):
# Returns True if no calls through API classes in implementation.
temp_dir = tempfile.mkdtemp()
# Extract API class files from jar
jar_cmd = [os.path.relpath(JAR_PATH, temp_dir), 'xf',
os.path.abspath(opts.api_jar)]
build_utils.CheckOutput(jar_cmd, cwd=temp_dir)
shutil.rmtree(os.path.join(temp_dir, 'META-INF'), ignore_errors=True)
# Collect names of API classes
api_classes = []
for dirpath, _, filenames in os.walk(temp_dir):
if not filenames:
continue
package = os.path.relpath(dirpath, temp_dir)
for filename in filenames:
if filename.endswith('.class'):
classname = filename[:-len('.class')]
api_classes += [os.path.normpath(os.path.join(package, classname))]
shutil.rmtree(temp_dir)
temp_dir = tempfile.mkdtemp()
# Extract impl class files from jars
for impl_jar in opts.impl_jar:
jar_cmd = [os.path.relpath(JAR_PATH, temp_dir), 'xf',
os.path.abspath(impl_jar)]
build_utils.CheckOutput(jar_cmd, cwd=temp_dir)
shutil.rmtree(os.path.join(temp_dir, 'META-INF'), ignore_errors=True)
# Process classes
bad_api_calls = []
for dirpath, _, filenames in os.walk(temp_dir):
if not filenames:
continue
# Dump classes
dump_file = os.path.join(temp_dir, 'dump.txt')
javap_cmd = '%s -private -c %s > %s' % (JAVAP_PATH, ' '.join(
os.path.join(dirpath, f)
for f in filenames).replace('$', '\\$'), dump_file)
if os.system(javap_cmd):
print('ERROR: javap failed on ' + ' '.join(filenames))
return False
# Process class dump
with open(dump_file, 'r') as dump:
find_api_calls(dump, api_classes, bad_api_calls)
shutil.rmtree(temp_dir)
if bad_api_calls:
print('ERROR: Found the following calls from implementation classes '
'through')
print(' API classes. These could fail if older API is used that')
print(' does not contain newer methods. Please call through a')
print(' wrapper class from VersionSafeCallbacks.')
print('\n'.join(bad_api_calls))
return not bad_api_calls
def check_api_version(opts):
if update_api.check_up_to_date(opts.api_jar):
return True
print('ERROR: API file out of date. Please run this command:')
print(' components/cronet/tools/update_api.py --api_jar %s' %
(os.path.abspath(opts.api_jar)))
return False
def main(args):
parser = argparse.ArgumentParser(
description='Enforce Cronet API requirements.')
parser.add_argument('--api_jar',
help='Path to API jar (i.e. cronet_api.jar)',
required=True,
metavar='path/to/cronet_api.jar')
parser.add_argument('--impl_jar',
help='Path to implementation jar '
'(i.e. cronet_impl_native_java.jar)',
required=True,
metavar='path/to/cronet_impl_native_java.jar',
action='append')
parser.add_argument('--stamp', help='Path to touch on success.')
opts = parser.parse_args(args)
ret = True
ret = check_api_calls(opts) and ret
ret = check_api_version(opts) and ret
if ret and opts.stamp:
build_utils.Touch(opts.stamp)
return ret
if __name__ == '__main__':
sys.exit(0 if main(sys.argv[1:]) else -1)
|