Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(77)

Side by Side Diff: components/cronet/tools/api_static_checks.py

Issue 2440613003: [Cronet] Enforce implementation does not call through API classes (Closed)
Patch Set: address comments Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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)
OLDNEW
« no previous file with comments | « components/cronet/tools/__init__.py ('k') | components/cronet/tools/api_static_checks_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698