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

Unified Diff: components/cronet/tools/api_static_checks.py

Issue 2440613003: [Cronet] Enforce implementation does not call through API classes (Closed)
Patch Set: add exception for Exception.getMessage() call Created 3 years, 12 months 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 side-by-side diff with in-line comments
Download patch
« 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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: components/cronet/tools/api_static_checks.py
diff --git a/components/cronet/tools/api_static_checks.py b/components/cronet/tools/api_static_checks.py
new file mode 100755
index 0000000000000000000000000000000000000000..360eaf641935defe73e17392dbd94b3c46d3f40d
--- /dev/null
+++ b/components/cronet/tools/api_static_checks.py
@@ -0,0 +1,184 @@
+#!/usr/bin/python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""api_static_checks.py - Check Cronet implementation does not call through
+API classes.
+"""
+
+import argparse
+import os
+import re
+import shutil
+import sys
+import tempfile
+
+REPOSITORY_ROOT = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), '..', '..', '..'))
+
+sys.path.append(os.path.join(REPOSITORY_ROOT, 'build/android/gyp/util'))
+import build_utils
+
+# 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.
+CLASS_RE = re.compile(r'.*class ([^ ]*) .*\{')
+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.impl.CronetEngineBuilderImpl/build ->'
+ ' org/chromium/net/ExperimentalCronetEngine/getVersionString:'
+ '()Ljava/lang/String;',
+'org.chromium.net.urlconnection.CronetFixedModeOutputStream$UploadDataProviderI'
+ 'mpl/read -> org/chromium/net/UploadDataSink/onReadSucceeded:(Z)V',
+'org.chromium.net.urlconnection.CronetFixedModeOutputStream$UploadDataProviderI'
+ 'mpl/rewind -> org/chromium/net/UploadDataSink/onRewindError:'
+ '(Ljava/lang/Exception;)V',
+'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect ->'
+ ' org/chromium/net/UrlRequest/cancel:()V',
+'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect ->'
+ ' org/chromium/net/UrlResponseInfo/getHttpStatusText:()Ljava/lang/String;',
+'org.chromium.net.urlconnection.CronetHttpURLConnection/disconnect ->'
+ ' org/chromium/net/UrlResponseInfo/getHttpStatusCode:()I',
+'org.chromium.net.urlconnection.CronetHttpURLConnection/getHeaderField ->'
+ ' org/chromium/net/UrlResponseInfo/getHttpStatusCode:()I',
+'org.chromium.net.urlconnection.CronetHttpURLConnection/getErrorStream ->'
+ ' org/chromium/net/UrlResponseInfo/getHttpStatusCode:()I',
+'org.chromium.net.urlconnection.CronetHttpURLConnection/setConnectTimeout ->'
+ ' org/chromium/net/UrlRequest/read:(Ljava/nio/ByteBuffer;)V',
+'org.chromium.net.urlconnection.CronetHttpURLConnection$CronetUrlRequestCallbac'
+ 'k/onRedirectReceived -> org/chromium/net/UrlRequest/followRedirect:()V',
+'org.chromium.net.urlconnection.CronetHttpURLConnection$CronetUrlRequestCallbac'
+ 'k/onRedirectReceived -> org/chromium/net/UrlRequest/cancel:()V',
+'org.chromium.net.urlconnection.CronetChunkedOutputStream$UploadDataProviderImp'
+ 'l/read -> org/chromium/net/UploadDataSink/onReadSucceeded:(Z)V',
+'org.chromium.net.urlconnection.CronetChunkedOutputStream$UploadDataProviderImp'
+ 'l/rewind -> org/chromium/net/UploadDataSink/onRewindError:'
+ '(Ljava/lang/Exception;)V',
+'org.chromium.net.urlconnection.CronetBufferedOutputStream$UploadDataProviderIm'
+ 'pl/read -> org/chromium/net/UploadDataSink/onReadSucceeded:(Z)V',
+'org.chromium.net.urlconnection.CronetBufferedOutputStream$UploadDataProviderIm'
+ 'pl/rewind -> org/chromium/net/UploadDataSink/onRewindSucceeded:()V',
+# getMessage() is an java.lang.Exception member, and so cannot be removed.
+'org.chromium.net.impl.NetworkExceptionImpl/getMessage -> '
+ 'org/chromium/net/NetworkException/getMessage:()Ljava/lang/String;',
+]
+
+
+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 line in dump:
+ if CLASS_RE.match(line):
+ caller_class = CLASS_RE.match(line).group(1)
+ if METHOD_RE.match(line):
+ caller_method = METHOD_RE.match(line).group(1)
+ if line[8:16] == ': invoke':
+ 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]
+
+
+def main(args):
+ # Returns True if no calls through API classes in implementation.
+
+ parser = argparse.ArgumentParser(
+ description='Check modules do not contain ARM Neon instructions.')
+ 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)
+
+ temp_dir = tempfile.mkdtemp()
+
+ # Extract API class files from jar
+ jar_cmd = ['jar', 'xf', os.path.abspath(opts.api_jar)]
+ build_utils.CheckOutput(jar_cmd, cwd=temp_dir)
+ shutil.rmtree(os.path.join(temp_dir, 'META-INF'))
+
+ # 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 = ['jar', 'xf', os.path.abspath(impl_jar)]
+ build_utils.CheckOutput(jar_cmd, cwd=temp_dir)
+ shutil.rmtree(os.path.join(temp_dir, 'META-INF'))
+
+ # 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')
+ if os.system('javap -c %s > %s' % (
+ ' '.join(os.path.join(dirpath, f) for f in filenames).replace(
+ '$', '\\$'),
+ dump_file)):
+ 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)
+
+ if not bad_api_calls and opts.stamp:
+ build_utils.Touch(opts.stamp)
+ return not bad_api_calls
+
+
+if __name__ == '__main__':
+ sys.exit(0 if main(sys.argv[1:]) else -1)
« 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