mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-12-01 09:46:09 +03:00
1406 lines
49 KiB
Python
1406 lines
49 KiB
Python
|
#!/usr/bin/env python
|
||
|
# Copyright (c) 2012 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.
|
||
|
|
||
|
"""Extracts native methods from a Java file and generates the JNI bindings.
|
||
|
If you change this, please run and update the tests."""
|
||
|
|
||
|
import collections
|
||
|
import errno
|
||
|
import optparse
|
||
|
import os
|
||
|
import re
|
||
|
from string import Template
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import textwrap
|
||
|
import zipfile
|
||
|
|
||
|
CHROMIUM_SRC = os.path.join(
|
||
|
os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
|
||
|
BUILD_ANDROID_GYP = os.path.join(
|
||
|
CHROMIUM_SRC, 'build', 'android', 'gyp')
|
||
|
|
||
|
sys.path.append(BUILD_ANDROID_GYP)
|
||
|
|
||
|
from util import build_utils
|
||
|
|
||
|
|
||
|
# Match single line comments, multiline comments, character literals, and
|
||
|
# double-quoted strings.
|
||
|
_COMMENT_REMOVER_REGEX = re.compile(
|
||
|
r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
|
||
|
re.DOTALL | re.MULTILINE)
|
||
|
|
||
|
_EXTRACT_NATIVES_REGEX = re.compile(
|
||
|
r'(@NativeClassQualifiedName'
|
||
|
r'\(\"(?P<native_class_name>.*?)\"\)\s+)?'
|
||
|
r'(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
|
||
|
r'(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
|
||
|
r'(?P<return_type>\S*) '
|
||
|
r'(?P<name>native\w+)\((?P<params>.*?)\);')
|
||
|
|
||
|
_MAIN_DEX_REGEX = re.compile(
|
||
|
r'^\s*(?:@(?:\w+\.)*\w+\s+)*@MainDex\b',
|
||
|
re.MULTILINE)
|
||
|
|
||
|
# Use 100 columns rather than 80 because it makes many lines more readable.
|
||
|
_WRAP_LINE_LENGTH = 100
|
||
|
# WrapOutput() is fairly slow. Pre-creating TextWrappers helps a bit.
|
||
|
_WRAPPERS_BY_INDENT = [
|
||
|
textwrap.TextWrapper(width=_WRAP_LINE_LENGTH, expand_tabs=False,
|
||
|
replace_whitespace=False,
|
||
|
subsequent_indent=' ' * (indent + 4),
|
||
|
break_long_words=False)
|
||
|
for indent in xrange(50)] # 50 chosen experimentally.
|
||
|
|
||
|
|
||
|
class ParseError(Exception):
|
||
|
"""Exception thrown when we can't parse the input file."""
|
||
|
|
||
|
def __init__(self, description, *context_lines):
|
||
|
Exception.__init__(self)
|
||
|
self.description = description
|
||
|
self.context_lines = context_lines
|
||
|
|
||
|
def __str__(self):
|
||
|
context = '\n'.join(self.context_lines)
|
||
|
return '***\nERROR: %s\n\n%s\n***' % (self.description, context)
|
||
|
|
||
|
|
||
|
class Param(object):
|
||
|
"""Describes a param for a method, either java or native."""
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
self.datatype = kwargs['datatype']
|
||
|
self.name = kwargs['name']
|
||
|
|
||
|
|
||
|
class NativeMethod(object):
|
||
|
"""Describes a C/C++ method that is called by Java code"""
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
self.static = kwargs['static']
|
||
|
self.java_class_name = kwargs['java_class_name']
|
||
|
self.return_type = kwargs['return_type']
|
||
|
self.name = kwargs['name']
|
||
|
self.params = kwargs['params']
|
||
|
if self.params:
|
||
|
assert type(self.params) is list
|
||
|
assert type(self.params[0]) is Param
|
||
|
if (self.params and
|
||
|
self.params[0].datatype == kwargs.get('ptr_type', 'int') and
|
||
|
self.params[0].name.startswith('native')):
|
||
|
self.type = 'method'
|
||
|
self.p0_type = self.params[0].name[len('native'):]
|
||
|
if kwargs.get('native_class_name'):
|
||
|
self.p0_type = kwargs['native_class_name']
|
||
|
else:
|
||
|
self.type = 'function'
|
||
|
self.method_id_var_name = kwargs.get('method_id_var_name', None)
|
||
|
|
||
|
|
||
|
class CalledByNative(object):
|
||
|
"""Describes a java method exported to c/c++"""
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
self.system_class = kwargs['system_class']
|
||
|
self.unchecked = kwargs['unchecked']
|
||
|
self.static = kwargs['static']
|
||
|
self.java_class_name = kwargs['java_class_name']
|
||
|
self.return_type = kwargs['return_type']
|
||
|
self.name = kwargs['name']
|
||
|
self.params = kwargs['params']
|
||
|
self.method_id_var_name = kwargs.get('method_id_var_name', None)
|
||
|
self.signature = kwargs.get('signature')
|
||
|
self.is_constructor = kwargs.get('is_constructor', False)
|
||
|
self.env_call = GetEnvCall(self.is_constructor, self.static,
|
||
|
self.return_type)
|
||
|
self.static_cast = GetStaticCastForReturnType(self.return_type)
|
||
|
|
||
|
|
||
|
class ConstantField(object):
|
||
|
def __init__(self, **kwargs):
|
||
|
self.name = kwargs['name']
|
||
|
self.value = kwargs['value']
|
||
|
|
||
|
|
||
|
def JavaDataTypeToC(java_type):
|
||
|
"""Returns a C datatype for the given java type."""
|
||
|
java_pod_type_map = {
|
||
|
'int': 'jint',
|
||
|
'byte': 'jbyte',
|
||
|
'char': 'jchar',
|
||
|
'short': 'jshort',
|
||
|
'boolean': 'jboolean',
|
||
|
'long': 'jlong',
|
||
|
'double': 'jdouble',
|
||
|
'float': 'jfloat',
|
||
|
}
|
||
|
java_type_map = {
|
||
|
'void': 'void',
|
||
|
'String': 'jstring',
|
||
|
'Class': 'jclass',
|
||
|
'Throwable': 'jthrowable',
|
||
|
'java/lang/String': 'jstring',
|
||
|
'java/lang/Class': 'jclass',
|
||
|
'java/lang/Throwable': 'jthrowable',
|
||
|
}
|
||
|
|
||
|
java_type = _StripGenerics(java_type)
|
||
|
if java_type in java_pod_type_map:
|
||
|
return java_pod_type_map[java_type]
|
||
|
elif java_type in java_type_map:
|
||
|
return java_type_map[java_type]
|
||
|
elif java_type.endswith('[]'):
|
||
|
if java_type[:-2] in java_pod_type_map:
|
||
|
return java_pod_type_map[java_type[:-2]] + 'Array'
|
||
|
return 'jobjectArray'
|
||
|
else:
|
||
|
return 'jobject'
|
||
|
|
||
|
|
||
|
def WrapCTypeForDeclaration(c_type):
|
||
|
"""Wrap the C datatype in a JavaRef if required."""
|
||
|
if re.match(RE_SCOPED_JNI_TYPES, c_type):
|
||
|
return 'const base::android::JavaParamRef<' + c_type + '>&'
|
||
|
else:
|
||
|
return c_type
|
||
|
|
||
|
|
||
|
def _JavaDataTypeToCForDeclaration(java_type):
|
||
|
"""Returns a JavaRef-wrapped C datatype for the given java type."""
|
||
|
return WrapCTypeForDeclaration(JavaDataTypeToC(java_type))
|
||
|
|
||
|
|
||
|
def JavaDataTypeToCForCalledByNativeParam(java_type):
|
||
|
"""Returns a C datatype to be when calling from native."""
|
||
|
if java_type == 'int':
|
||
|
return 'JniIntWrapper'
|
||
|
else:
|
||
|
c_type = JavaDataTypeToC(java_type)
|
||
|
if re.match(RE_SCOPED_JNI_TYPES, c_type):
|
||
|
return 'const base::android::JavaRef<' + c_type + '>&'
|
||
|
else:
|
||
|
return c_type
|
||
|
|
||
|
|
||
|
def JavaReturnValueToC(java_type):
|
||
|
"""Returns a valid C return value for the given java type."""
|
||
|
java_pod_type_map = {
|
||
|
'int': '0',
|
||
|
'byte': '0',
|
||
|
'char': '0',
|
||
|
'short': '0',
|
||
|
'boolean': 'false',
|
||
|
'long': '0',
|
||
|
'double': '0',
|
||
|
'float': '0',
|
||
|
'void': ''
|
||
|
}
|
||
|
return java_pod_type_map.get(java_type, 'NULL')
|
||
|
|
||
|
|
||
|
def _GetJNIFirstParamType(native):
|
||
|
if native.type == 'function' and native.static:
|
||
|
return 'jclass'
|
||
|
return 'jobject'
|
||
|
|
||
|
|
||
|
def _GetJNIFirstParam(native, for_declaration):
|
||
|
c_type = _GetJNIFirstParamType(native)
|
||
|
if for_declaration:
|
||
|
c_type = WrapCTypeForDeclaration(c_type)
|
||
|
return [c_type + ' jcaller']
|
||
|
|
||
|
|
||
|
def _GetParamsInDeclaration(native):
|
||
|
"""Returns the params for the forward declaration.
|
||
|
|
||
|
Args:
|
||
|
native: the native dictionary describing the method.
|
||
|
|
||
|
Returns:
|
||
|
A string containing the params.
|
||
|
"""
|
||
|
return ',\n '.join(_GetJNIFirstParam(native, True) +
|
||
|
[_JavaDataTypeToCForDeclaration(param.datatype) + ' ' +
|
||
|
param.name
|
||
|
for param in native.params])
|
||
|
|
||
|
|
||
|
def GetParamsInStub(native):
|
||
|
"""Returns the params for the stub declaration.
|
||
|
|
||
|
Args:
|
||
|
native: the native dictionary describing the method.
|
||
|
|
||
|
Returns:
|
||
|
A string containing the params.
|
||
|
"""
|
||
|
params = [JavaDataTypeToC(p.datatype) + ' ' + p.name for p in native.params]
|
||
|
return ',\n '.join(_GetJNIFirstParam(native, False) + params)
|
||
|
|
||
|
|
||
|
def _StripGenerics(value):
|
||
|
"""Strips Java generics from a string."""
|
||
|
nest_level = 0 # How deeply we are nested inside the generics.
|
||
|
start_index = 0 # Starting index of the last non-generic region.
|
||
|
out = []
|
||
|
|
||
|
for i, c in enumerate(value):
|
||
|
if c == '<':
|
||
|
if nest_level == 0:
|
||
|
out.append(value[start_index:i])
|
||
|
nest_level += 1
|
||
|
elif c == '>':
|
||
|
start_index = i + 1
|
||
|
nest_level -= 1
|
||
|
out.append(value[start_index:])
|
||
|
|
||
|
return ''.join(out)
|
||
|
|
||
|
|
||
|
class JniParams(object):
|
||
|
"""Get JNI related parameters."""
|
||
|
|
||
|
def __init__(self, fully_qualified_class):
|
||
|
self._fully_qualified_class = 'L' + fully_qualified_class
|
||
|
self._package = '/'.join(fully_qualified_class.split('/')[:-1])
|
||
|
self._imports = []
|
||
|
self._inner_classes = []
|
||
|
self._implicit_imports = []
|
||
|
|
||
|
def ExtractImportsAndInnerClasses(self, contents):
|
||
|
contents = contents.replace('\n', '')
|
||
|
re_import = re.compile(r'import.*?(?P<class>\S*?);')
|
||
|
for match in re.finditer(re_import, contents):
|
||
|
self._imports += ['L' + match.group('class').replace('.', '/')]
|
||
|
|
||
|
re_inner = re.compile(r'(class|interface|enum)\s+?(?P<name>\w+?)\W')
|
||
|
for match in re.finditer(re_inner, contents):
|
||
|
inner = match.group('name')
|
||
|
if not self._fully_qualified_class.endswith(inner):
|
||
|
self._inner_classes += [self._fully_qualified_class + '$' +
|
||
|
inner]
|
||
|
|
||
|
re_additional_imports = re.compile(
|
||
|
r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
|
||
|
for match in re.finditer(re_additional_imports, contents):
|
||
|
for class_name in match.group('class_names').split(','):
|
||
|
self._AddAdditionalImport(class_name.strip())
|
||
|
|
||
|
def JavaToJni(self, param):
|
||
|
"""Converts a java param into a JNI signature type."""
|
||
|
pod_param_map = {
|
||
|
'int': 'I',
|
||
|
'boolean': 'Z',
|
||
|
'char': 'C',
|
||
|
'short': 'S',
|
||
|
'long': 'J',
|
||
|
'double': 'D',
|
||
|
'float': 'F',
|
||
|
'byte': 'B',
|
||
|
'void': 'V',
|
||
|
}
|
||
|
object_param_list = [
|
||
|
'Ljava/lang/Boolean',
|
||
|
'Ljava/lang/Integer',
|
||
|
'Ljava/lang/Long',
|
||
|
'Ljava/lang/Object',
|
||
|
'Ljava/lang/String',
|
||
|
'Ljava/lang/Class',
|
||
|
'Ljava/lang/CharSequence',
|
||
|
'Ljava/lang/Runnable',
|
||
|
'Ljava/lang/Throwable',
|
||
|
]
|
||
|
|
||
|
prefix = ''
|
||
|
# Array?
|
||
|
while param[-2:] == '[]':
|
||
|
prefix += '['
|
||
|
param = param[:-2]
|
||
|
# Generic?
|
||
|
if '<' in param:
|
||
|
param = param[:param.index('<')]
|
||
|
if param in pod_param_map:
|
||
|
return prefix + pod_param_map[param]
|
||
|
if '/' in param:
|
||
|
# Coming from javap, use the fully qualified param directly.
|
||
|
return prefix + 'L' + param + ';'
|
||
|
|
||
|
for qualified_name in (object_param_list +
|
||
|
[self._fully_qualified_class] + self._inner_classes):
|
||
|
if (qualified_name.endswith('/' + param) or
|
||
|
qualified_name.endswith('$' + param.replace('.', '$')) or
|
||
|
qualified_name == 'L' + param):
|
||
|
return prefix + qualified_name + ';'
|
||
|
|
||
|
# Is it from an import? (e.g. referecing Class from import pkg.Class;
|
||
|
# note that referencing an inner class Inner from import pkg.Class.Inner
|
||
|
# is not supported).
|
||
|
for qualified_name in self._imports:
|
||
|
if qualified_name.endswith('/' + param):
|
||
|
# Ensure it's not an inner class.
|
||
|
components = qualified_name.split('/')
|
||
|
if len(components) > 2 and components[-2][0].isupper():
|
||
|
raise SyntaxError('Inner class (%s) can not be imported '
|
||
|
'and used by JNI (%s). Please import the outer '
|
||
|
'class and use Outer.Inner instead.' %
|
||
|
(qualified_name, param))
|
||
|
return prefix + qualified_name + ';'
|
||
|
|
||
|
# Is it an inner class from an outer class import? (e.g. referencing
|
||
|
# Class.Inner from import pkg.Class).
|
||
|
if '.' in param:
|
||
|
components = param.split('.')
|
||
|
outer = '/'.join(components[:-1])
|
||
|
inner = components[-1]
|
||
|
for qualified_name in self._imports:
|
||
|
if qualified_name.endswith('/' + outer):
|
||
|
return (prefix + qualified_name + '$' + inner + ';')
|
||
|
raise SyntaxError('Inner class (%s) can not be '
|
||
|
'used directly by JNI. Please import the outer '
|
||
|
'class, probably:\n'
|
||
|
'import %s.%s;' %
|
||
|
(param, self._package.replace('/', '.'),
|
||
|
outer.replace('/', '.')))
|
||
|
|
||
|
self._CheckImplicitImports(param)
|
||
|
|
||
|
# Type not found, falling back to same package as this class.
|
||
|
return (prefix + 'L' + self._package + '/' + param + ';')
|
||
|
|
||
|
def _AddAdditionalImport(self, class_name):
|
||
|
assert class_name.endswith('.class')
|
||
|
raw_class_name = class_name[:-len('.class')]
|
||
|
if '.' in raw_class_name:
|
||
|
raise SyntaxError('%s cannot be used in @JNIAdditionalImport. '
|
||
|
'Only import unqualified outer classes.' % class_name)
|
||
|
new_import = 'L%s/%s' % (self._package, raw_class_name)
|
||
|
if new_import in self._imports:
|
||
|
raise SyntaxError('Do not use JNIAdditionalImport on an already '
|
||
|
'imported class: %s' % (new_import.replace('/', '.')))
|
||
|
self._imports += [new_import]
|
||
|
|
||
|
def _CheckImplicitImports(self, param):
|
||
|
# Ensure implicit imports, such as java.lang.*, are not being treated
|
||
|
# as being in the same package.
|
||
|
if not self._implicit_imports:
|
||
|
# This file was generated from android.jar and lists
|
||
|
# all classes that are implicitly imported.
|
||
|
with file(os.path.join(os.path.dirname(sys.argv[0]),
|
||
|
'android_jar.classes'), 'r') as f:
|
||
|
self._implicit_imports = f.readlines()
|
||
|
for implicit_import in self._implicit_imports:
|
||
|
implicit_import = implicit_import.strip().replace('.class', '')
|
||
|
implicit_import = implicit_import.replace('/', '.')
|
||
|
if implicit_import.endswith('.' + param):
|
||
|
raise SyntaxError('Ambiguous class (%s) can not be used directly '
|
||
|
'by JNI.\nPlease import it, probably:\n\n'
|
||
|
'import %s;' %
|
||
|
(param, implicit_import))
|
||
|
|
||
|
def Signature(self, params, returns):
|
||
|
"""Returns the JNI signature for the given datatypes."""
|
||
|
items = ['(']
|
||
|
items += [self.JavaToJni(param.datatype) for param in params]
|
||
|
items += [')']
|
||
|
items += [self.JavaToJni(returns)]
|
||
|
return '"{}"'.format(''.join(items))
|
||
|
|
||
|
@staticmethod
|
||
|
def ParseJavaPSignature(signature_line):
|
||
|
prefix = 'Signature: '
|
||
|
index = signature_line.find(prefix)
|
||
|
if index == -1:
|
||
|
prefix = 'descriptor: '
|
||
|
index = signature_line.index(prefix)
|
||
|
return '"%s"' % signature_line[index + len(prefix):]
|
||
|
|
||
|
@staticmethod
|
||
|
def Parse(params):
|
||
|
"""Parses the params into a list of Param objects."""
|
||
|
if not params:
|
||
|
return []
|
||
|
ret = []
|
||
|
params = _StripGenerics(params)
|
||
|
for p in params.split(','):
|
||
|
items = p.split()
|
||
|
|
||
|
# Remove @Annotations from parameters.
|
||
|
while items[0].startswith('@'):
|
||
|
del items[0]
|
||
|
|
||
|
if 'final' in items:
|
||
|
items.remove('final')
|
||
|
|
||
|
param = Param(
|
||
|
datatype=items[0],
|
||
|
name=(items[1] if len(items) > 1 else 'p%s' % len(ret)),
|
||
|
)
|
||
|
ret += [param]
|
||
|
return ret
|
||
|
|
||
|
|
||
|
def ExtractJNINamespace(contents):
|
||
|
re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)')
|
||
|
m = re.findall(re_jni_namespace, contents)
|
||
|
if not m:
|
||
|
return ''
|
||
|
return m[0]
|
||
|
|
||
|
|
||
|
def ExtractFullyQualifiedJavaClassName(java_file_name, contents):
|
||
|
re_package = re.compile('.*?package (.*?);')
|
||
|
matches = re.findall(re_package, contents)
|
||
|
if not matches:
|
||
|
raise SyntaxError('Unable to find "package" line in %s' % java_file_name)
|
||
|
return (matches[0].replace('.', '/') + '/' +
|
||
|
os.path.splitext(os.path.basename(java_file_name))[0])
|
||
|
|
||
|
|
||
|
def ExtractNatives(contents, ptr_type):
|
||
|
"""Returns a list of dict containing information about a native method."""
|
||
|
contents = contents.replace('\n', '')
|
||
|
natives = []
|
||
|
for match in _EXTRACT_NATIVES_REGEX.finditer(contents):
|
||
|
native = NativeMethod(
|
||
|
static='static' in match.group('qualifiers'),
|
||
|
java_class_name=match.group('java_class_name'),
|
||
|
native_class_name=match.group('native_class_name'),
|
||
|
return_type=match.group('return_type'),
|
||
|
name=match.group('name').replace('native', ''),
|
||
|
params=JniParams.Parse(match.group('params')),
|
||
|
ptr_type=ptr_type)
|
||
|
natives += [native]
|
||
|
return natives
|
||
|
|
||
|
|
||
|
def IsMainDexJavaClass(contents):
|
||
|
"""Returns True if the class or any of its methods are annotated as @MainDex.
|
||
|
|
||
|
JNI registration doesn't always need to be completed for non-browser processes
|
||
|
since most Java code is only used by the browser process. Classes that are
|
||
|
needed by non-browser processes must explicitly be annotated with @MainDex
|
||
|
to force JNI registration.
|
||
|
"""
|
||
|
return bool(_MAIN_DEX_REGEX.search(contents))
|
||
|
|
||
|
|
||
|
def GetBinaryClassName(fully_qualified_class):
|
||
|
"""Returns a string concatenating the Java package and class."""
|
||
|
escaped = fully_qualified_class.replace('_', '_1')
|
||
|
return escaped.replace('/', '_').replace('$', '_00024')
|
||
|
|
||
|
|
||
|
def GetRegistrationFunctionName(fully_qualified_class):
|
||
|
"""Returns the register name with a given class."""
|
||
|
return 'RegisterNative_' + GetBinaryClassName(fully_qualified_class)
|
||
|
|
||
|
|
||
|
def GetStaticCastForReturnType(return_type):
|
||
|
type_map = { 'String' : 'jstring',
|
||
|
'java/lang/String' : 'jstring',
|
||
|
'Class': 'jclass',
|
||
|
'java/lang/Class': 'jclass',
|
||
|
'Throwable': 'jthrowable',
|
||
|
'java/lang/Throwable': 'jthrowable',
|
||
|
'boolean[]': 'jbooleanArray',
|
||
|
'byte[]': 'jbyteArray',
|
||
|
'char[]': 'jcharArray',
|
||
|
'short[]': 'jshortArray',
|
||
|
'int[]': 'jintArray',
|
||
|
'long[]': 'jlongArray',
|
||
|
'float[]': 'jfloatArray',
|
||
|
'double[]': 'jdoubleArray' }
|
||
|
return_type = _StripGenerics(return_type)
|
||
|
ret = type_map.get(return_type, None)
|
||
|
if ret:
|
||
|
return ret
|
||
|
if return_type.endswith('[]'):
|
||
|
return 'jobjectArray'
|
||
|
return None
|
||
|
|
||
|
|
||
|
def GetEnvCall(is_constructor, is_static, return_type):
|
||
|
"""Maps the types availabe via env->Call__Method."""
|
||
|
if is_constructor:
|
||
|
return 'NewObject'
|
||
|
env_call_map = {'boolean': 'Boolean',
|
||
|
'byte': 'Byte',
|
||
|
'char': 'Char',
|
||
|
'short': 'Short',
|
||
|
'int': 'Int',
|
||
|
'long': 'Long',
|
||
|
'float': 'Float',
|
||
|
'void': 'Void',
|
||
|
'double': 'Double',
|
||
|
'Object': 'Object',
|
||
|
}
|
||
|
call = env_call_map.get(return_type, 'Object')
|
||
|
if is_static:
|
||
|
call = 'Static' + call
|
||
|
return 'Call' + call + 'Method'
|
||
|
|
||
|
|
||
|
def GetMangledParam(datatype):
|
||
|
"""Returns a mangled identifier for the datatype."""
|
||
|
if len(datatype) <= 2:
|
||
|
return datatype.replace('[', 'A')
|
||
|
ret = ''
|
||
|
for i in range(1, len(datatype)):
|
||
|
c = datatype[i]
|
||
|
if c == '[':
|
||
|
ret += 'A'
|
||
|
elif c.isupper() or datatype[i - 1] in ['/', 'L']:
|
||
|
ret += c.upper()
|
||
|
return ret
|
||
|
|
||
|
|
||
|
def GetMangledMethodName(jni_params, name, params, return_type):
|
||
|
"""Returns a mangled method name for the given signature.
|
||
|
|
||
|
The returned name can be used as a C identifier and will be unique for all
|
||
|
valid overloads of the same method.
|
||
|
|
||
|
Args:
|
||
|
jni_params: JniParams object.
|
||
|
name: string.
|
||
|
params: list of Param.
|
||
|
return_type: string.
|
||
|
|
||
|
Returns:
|
||
|
A mangled name.
|
||
|
"""
|
||
|
mangled_items = []
|
||
|
for datatype in [return_type] + [x.datatype for x in params]:
|
||
|
mangled_items += [GetMangledParam(jni_params.JavaToJni(datatype))]
|
||
|
mangled_name = name + '_'.join(mangled_items)
|
||
|
assert re.match(r'[0-9a-zA-Z_]+', mangled_name)
|
||
|
return mangled_name
|
||
|
|
||
|
|
||
|
def MangleCalledByNatives(jni_params, called_by_natives):
|
||
|
"""Mangles all the overloads from the call_by_natives list."""
|
||
|
method_counts = collections.defaultdict(
|
||
|
lambda: collections.defaultdict(lambda: 0))
|
||
|
for called_by_native in called_by_natives:
|
||
|
java_class_name = called_by_native.java_class_name
|
||
|
name = called_by_native.name
|
||
|
method_counts[java_class_name][name] += 1
|
||
|
for called_by_native in called_by_natives:
|
||
|
java_class_name = called_by_native.java_class_name
|
||
|
method_name = called_by_native.name
|
||
|
method_id_var_name = method_name
|
||
|
if method_counts[java_class_name][method_name] > 1:
|
||
|
method_id_var_name = GetMangledMethodName(jni_params, method_name,
|
||
|
called_by_native.params,
|
||
|
called_by_native.return_type)
|
||
|
called_by_native.method_id_var_name = method_id_var_name
|
||
|
return called_by_natives
|
||
|
|
||
|
|
||
|
# Regex to match the JNI types that should be wrapped in a JavaRef.
|
||
|
RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*Array')
|
||
|
|
||
|
|
||
|
# Regex to match a string like "@CalledByNative public void foo(int bar)".
|
||
|
RE_CALLED_BY_NATIVE = re.compile(
|
||
|
r'@CalledByNative(?P<Unchecked>(?:Unchecked)?)(?:\("(?P<annotation>.*)"\))?'
|
||
|
r'(?:\s+@\w+(?:\(.*\))?)*' # Ignore any other annotations.
|
||
|
r'\s+(?P<prefix>('
|
||
|
r'(private|protected|public|static|abstract|final|default|synchronized)'
|
||
|
r'\s*)*)'
|
||
|
r'(?:\s*@\w+)?' # Ignore annotations in return types.
|
||
|
r'\s*(?P<return_type>\S*?)'
|
||
|
r'\s*(?P<name>\w+)'
|
||
|
r'\s*\((?P<params>[^\)]*)\)')
|
||
|
|
||
|
# Removes empty lines that are indented (i.e. start with 2x spaces).
|
||
|
def RemoveIndentedEmptyLines(string):
|
||
|
return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE)
|
||
|
|
||
|
|
||
|
def ExtractCalledByNatives(jni_params, contents):
|
||
|
"""Parses all methods annotated with @CalledByNative.
|
||
|
|
||
|
Args:
|
||
|
jni_params: JniParams object.
|
||
|
contents: the contents of the java file.
|
||
|
|
||
|
Returns:
|
||
|
A list of dict with information about the annotated methods.
|
||
|
TODO(bulach): return a CalledByNative object.
|
||
|
|
||
|
Raises:
|
||
|
ParseError: if unable to parse.
|
||
|
"""
|
||
|
called_by_natives = []
|
||
|
for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
|
||
|
return_type = match.group('return_type')
|
||
|
name = match.group('name')
|
||
|
if not return_type:
|
||
|
is_constructor = True
|
||
|
return_type = name
|
||
|
name = "Constructor"
|
||
|
else:
|
||
|
is_constructor = False
|
||
|
|
||
|
called_by_natives += [CalledByNative(
|
||
|
system_class=False,
|
||
|
unchecked='Unchecked' in match.group('Unchecked'),
|
||
|
static='static' in match.group('prefix'),
|
||
|
java_class_name=match.group('annotation') or '',
|
||
|
return_type=return_type,
|
||
|
name=name,
|
||
|
is_constructor=is_constructor,
|
||
|
params=JniParams.Parse(match.group('params')))]
|
||
|
# Check for any @CalledByNative occurrences that weren't matched.
|
||
|
unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n')
|
||
|
for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]):
|
||
|
if '@CalledByNative' in line1:
|
||
|
raise ParseError('could not parse @CalledByNative method signature',
|
||
|
line1, line2)
|
||
|
return MangleCalledByNatives(jni_params, called_by_natives)
|
||
|
|
||
|
|
||
|
def RemoveComments(contents):
|
||
|
# We need to support both inline and block comments, and we need to handle
|
||
|
# strings that contain '//' or '/*'.
|
||
|
# TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java
|
||
|
# parser. Maybe we could ditch JNIFromJavaSource and just always use
|
||
|
# JNIFromJavaP; or maybe we could rewrite this script in Java and use APT.
|
||
|
# http://code.google.com/p/chromium/issues/detail?id=138941
|
||
|
def replacer(match):
|
||
|
# Replace matches that are comments with nothing; return literals/strings
|
||
|
# unchanged.
|
||
|
s = match.group(0)
|
||
|
if s.startswith('/'):
|
||
|
return ''
|
||
|
else:
|
||
|
return s
|
||
|
return _COMMENT_REMOVER_REGEX.sub(replacer, contents)
|
||
|
|
||
|
|
||
|
class JNIFromJavaP(object):
|
||
|
"""Uses 'javap' to parse a .class file and generate the JNI header file."""
|
||
|
|
||
|
def __init__(self, contents, options):
|
||
|
self.contents = contents
|
||
|
self.namespace = options.namespace
|
||
|
for line in contents:
|
||
|
class_name = re.match(
|
||
|
'.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)',
|
||
|
line)
|
||
|
if class_name:
|
||
|
self.fully_qualified_class = class_name.group('class_name')
|
||
|
break
|
||
|
self.fully_qualified_class = self.fully_qualified_class.replace('.', '/')
|
||
|
# Java 7's javap includes type parameters in output, like HashSet<T>. Strip
|
||
|
# away the <...> and use the raw class name that Java 6 would've given us.
|
||
|
self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0]
|
||
|
self.jni_params = JniParams(self.fully_qualified_class)
|
||
|
self.java_class_name = self.fully_qualified_class.split('/')[-1]
|
||
|
if not self.namespace:
|
||
|
self.namespace = 'JNI_' + self.java_class_name
|
||
|
re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)'
|
||
|
'\((?P<params>.*?)\)')
|
||
|
self.called_by_natives = []
|
||
|
for lineno, content in enumerate(contents[2:], 2):
|
||
|
match = re.match(re_method, content)
|
||
|
if not match:
|
||
|
continue
|
||
|
self.called_by_natives += [CalledByNative(
|
||
|
system_class=True,
|
||
|
unchecked=False,
|
||
|
static='static' in match.group('prefix'),
|
||
|
java_class_name='',
|
||
|
return_type=match.group('return_type').replace('.', '/'),
|
||
|
name=match.group('name'),
|
||
|
params=JniParams.Parse(match.group('params').replace('.', '/')),
|
||
|
signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))]
|
||
|
re_constructor = re.compile('(.*?)public ' +
|
||
|
self.fully_qualified_class.replace('/', '.') +
|
||
|
'\((?P<params>.*?)\)')
|
||
|
for lineno, content in enumerate(contents[2:], 2):
|
||
|
match = re.match(re_constructor, content)
|
||
|
if not match:
|
||
|
continue
|
||
|
self.called_by_natives += [CalledByNative(
|
||
|
system_class=True,
|
||
|
unchecked=False,
|
||
|
static=False,
|
||
|
java_class_name='',
|
||
|
return_type=self.fully_qualified_class,
|
||
|
name='Constructor',
|
||
|
params=JniParams.Parse(match.group('params').replace('.', '/')),
|
||
|
signature=JniParams.ParseJavaPSignature(contents[lineno + 1]),
|
||
|
is_constructor=True)]
|
||
|
self.called_by_natives = MangleCalledByNatives(self.jni_params,
|
||
|
self.called_by_natives)
|
||
|
self.constant_fields = []
|
||
|
re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
|
||
|
re_constant_field_value = re.compile(
|
||
|
'.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)')
|
||
|
for lineno, content in enumerate(contents[2:], 2):
|
||
|
match = re.match(re_constant_field, content)
|
||
|
if not match:
|
||
|
continue
|
||
|
value = re.match(re_constant_field_value, contents[lineno + 2])
|
||
|
if not value:
|
||
|
value = re.match(re_constant_field_value, contents[lineno + 3])
|
||
|
if value:
|
||
|
self.constant_fields.append(
|
||
|
ConstantField(name=match.group('name'),
|
||
|
value=value.group('value')))
|
||
|
|
||
|
self.inl_header_file_generator = InlHeaderFileGenerator(
|
||
|
self.namespace, self.fully_qualified_class, [], self.called_by_natives,
|
||
|
self.constant_fields, self.jni_params, options)
|
||
|
|
||
|
def GetContent(self):
|
||
|
return self.inl_header_file_generator.GetContent()
|
||
|
|
||
|
@staticmethod
|
||
|
def CreateFromClass(class_file, options):
|
||
|
class_name = os.path.splitext(os.path.basename(class_file))[0]
|
||
|
p = subprocess.Popen(args=[options.javap, '-c', '-verbose',
|
||
|
'-s', class_name],
|
||
|
cwd=os.path.dirname(class_file),
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.PIPE)
|
||
|
stdout, _ = p.communicate()
|
||
|
jni_from_javap = JNIFromJavaP(stdout.split('\n'), options)
|
||
|
return jni_from_javap
|
||
|
|
||
|
|
||
|
class JNIFromJavaSource(object):
|
||
|
"""Uses the given java source file to generate the JNI header file."""
|
||
|
|
||
|
def __init__(self, contents, fully_qualified_class, options):
|
||
|
contents = RemoveComments(contents)
|
||
|
self.jni_params = JniParams(fully_qualified_class)
|
||
|
self.jni_params.ExtractImportsAndInnerClasses(contents)
|
||
|
jni_namespace = ExtractJNINamespace(contents) or options.namespace
|
||
|
natives = ExtractNatives(contents, options.ptr_type)
|
||
|
called_by_natives = ExtractCalledByNatives(self.jni_params, contents)
|
||
|
if len(natives) == 0 and len(called_by_natives) == 0:
|
||
|
raise SyntaxError('Unable to find any JNI methods for %s.' %
|
||
|
fully_qualified_class)
|
||
|
inl_header_file_generator = InlHeaderFileGenerator(
|
||
|
jni_namespace, fully_qualified_class, natives, called_by_natives, [],
|
||
|
self.jni_params, options)
|
||
|
self.content = inl_header_file_generator.GetContent()
|
||
|
|
||
|
def GetContent(self):
|
||
|
return self.content
|
||
|
|
||
|
@staticmethod
|
||
|
def CreateFromFile(java_file_name, options):
|
||
|
contents = file(java_file_name).read()
|
||
|
fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name,
|
||
|
contents)
|
||
|
return JNIFromJavaSource(contents, fully_qualified_class, options)
|
||
|
|
||
|
|
||
|
class HeaderFileGeneratorHelper(object):
|
||
|
"""Include helper methods for header generators."""
|
||
|
|
||
|
def __init__(self, class_name, fully_qualified_class):
|
||
|
self.class_name = class_name
|
||
|
self.fully_qualified_class = fully_qualified_class
|
||
|
|
||
|
def GetStubName(self, native):
|
||
|
"""Return the name of the stub function for this native method.
|
||
|
|
||
|
Args:
|
||
|
native: the native dictionary describing the method.
|
||
|
|
||
|
Returns:
|
||
|
A string with the stub function name (used by the JVM).
|
||
|
"""
|
||
|
template = Template("Java_${JAVA_NAME}_native${NAME}")
|
||
|
|
||
|
java_name = self.fully_qualified_class
|
||
|
if native.java_class_name:
|
||
|
java_name += '$' + native.java_class_name
|
||
|
|
||
|
values = {'NAME': native.name,
|
||
|
'JAVA_NAME': GetBinaryClassName(java_name)}
|
||
|
return template.substitute(values)
|
||
|
|
||
|
def GetUniqueClasses(self, origin):
|
||
|
ret = {self.class_name: self.fully_qualified_class}
|
||
|
for entry in origin:
|
||
|
class_name = self.class_name
|
||
|
jni_class_path = self.fully_qualified_class
|
||
|
if entry.java_class_name:
|
||
|
class_name = entry.java_class_name
|
||
|
jni_class_path = self.fully_qualified_class + '$' + class_name
|
||
|
ret[class_name] = jni_class_path
|
||
|
return ret
|
||
|
|
||
|
def GetClassPathLines(self, classes, declare_only=False):
|
||
|
"""Returns the ClassPath constants."""
|
||
|
ret = []
|
||
|
if declare_only:
|
||
|
template = Template("""
|
||
|
extern const char kClassPath_${JAVA_CLASS}[];
|
||
|
""")
|
||
|
else:
|
||
|
template = Template("""
|
||
|
JNI_REGISTRATION_EXPORT extern const char kClassPath_${JAVA_CLASS}[];
|
||
|
const char kClassPath_${JAVA_CLASS}[] = \
|
||
|
"${JNI_CLASS_PATH}";
|
||
|
""")
|
||
|
|
||
|
for full_clazz in classes.itervalues():
|
||
|
values = {
|
||
|
'JAVA_CLASS': GetBinaryClassName(full_clazz),
|
||
|
'JNI_CLASS_PATH': full_clazz,
|
||
|
}
|
||
|
ret += [template.substitute(values)]
|
||
|
|
||
|
class_getter = """\
|
||
|
#ifndef ${JAVA_CLASS}_clazz_defined
|
||
|
#define ${JAVA_CLASS}_clazz_defined
|
||
|
inline jclass ${JAVA_CLASS}_clazz(JNIEnv* env) {
|
||
|
return base::android::LazyGetClass(env, kClassPath_${JAVA_CLASS}, \
|
||
|
&g_${JAVA_CLASS}_clazz);
|
||
|
}
|
||
|
#endif
|
||
|
"""
|
||
|
if declare_only:
|
||
|
template = Template("""\
|
||
|
extern std::atomic<jclass> g_${JAVA_CLASS}_clazz;
|
||
|
""" + class_getter)
|
||
|
else:
|
||
|
template = Template("""\
|
||
|
// Leaking this jclass as we cannot use LazyInstance from some threads.
|
||
|
JNI_REGISTRATION_EXPORT std::atomic<jclass> g_${JAVA_CLASS}_clazz(nullptr);
|
||
|
""" + class_getter)
|
||
|
|
||
|
for full_clazz in classes.itervalues():
|
||
|
values = {
|
||
|
'JAVA_CLASS': GetBinaryClassName(full_clazz),
|
||
|
}
|
||
|
ret += [template.substitute(values)]
|
||
|
|
||
|
return ''.join(ret)
|
||
|
|
||
|
|
||
|
class InlHeaderFileGenerator(object):
|
||
|
"""Generates an inline header file for JNI integration."""
|
||
|
|
||
|
def __init__(self, namespace, fully_qualified_class, natives,
|
||
|
called_by_natives, constant_fields, jni_params, options):
|
||
|
self.namespace = namespace
|
||
|
self.fully_qualified_class = fully_qualified_class
|
||
|
self.class_name = self.fully_qualified_class.split('/')[-1]
|
||
|
self.natives = natives
|
||
|
self.called_by_natives = called_by_natives
|
||
|
self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
|
||
|
self.constant_fields = constant_fields
|
||
|
self.jni_params = jni_params
|
||
|
self.options = options
|
||
|
self.helper = HeaderFileGeneratorHelper(
|
||
|
self.class_name, fully_qualified_class)
|
||
|
|
||
|
|
||
|
def GetContent(self):
|
||
|
"""Returns the content of the JNI binding file."""
|
||
|
template = Template("""\
|
||
|
// Copyright 2014 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.
|
||
|
|
||
|
|
||
|
// This file is autogenerated by
|
||
|
// ${SCRIPT_NAME}
|
||
|
// For
|
||
|
// ${FULLY_QUALIFIED_CLASS}
|
||
|
|
||
|
#ifndef ${HEADER_GUARD}
|
||
|
#define ${HEADER_GUARD}
|
||
|
|
||
|
#include <jni.h>
|
||
|
|
||
|
${INCLUDES}
|
||
|
|
||
|
// Step 1: Forward declarations.
|
||
|
$CLASS_PATH_DEFINITIONS
|
||
|
|
||
|
// Step 2: Constants (optional).
|
||
|
|
||
|
$CONSTANT_FIELDS\
|
||
|
|
||
|
// Step 3: Method stubs.
|
||
|
$METHOD_STUBS
|
||
|
|
||
|
#endif // ${HEADER_GUARD}
|
||
|
""")
|
||
|
values = {
|
||
|
'SCRIPT_NAME': self.options.script_name,
|
||
|
'FULLY_QUALIFIED_CLASS': self.fully_qualified_class,
|
||
|
'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(),
|
||
|
'CONSTANT_FIELDS': self.GetConstantFieldsString(),
|
||
|
'METHOD_STUBS': self.GetMethodStubsString(),
|
||
|
'HEADER_GUARD': self.header_guard,
|
||
|
'INCLUDES': self.GetIncludesString(),
|
||
|
}
|
||
|
open_namespace = self.GetOpenNamespaceString()
|
||
|
if open_namespace:
|
||
|
close_namespace = self.GetCloseNamespaceString()
|
||
|
values['METHOD_STUBS'] = '\n'.join([
|
||
|
open_namespace, values['METHOD_STUBS'], close_namespace])
|
||
|
|
||
|
constant_fields = values['CONSTANT_FIELDS']
|
||
|
if constant_fields:
|
||
|
values['CONSTANT_FIELDS'] = '\n'.join([
|
||
|
open_namespace, constant_fields, close_namespace])
|
||
|
|
||
|
return WrapOutput(template.substitute(values))
|
||
|
|
||
|
def GetClassPathDefinitionsString(self):
|
||
|
classes = self.helper.GetUniqueClasses(self.called_by_natives)
|
||
|
classes.update(self.helper.GetUniqueClasses(self.natives))
|
||
|
return self.helper.GetClassPathLines(classes)
|
||
|
|
||
|
def GetConstantFieldsString(self):
|
||
|
if not self.constant_fields:
|
||
|
return ''
|
||
|
ret = ['enum Java_%s_constant_fields {' % self.class_name]
|
||
|
for c in self.constant_fields:
|
||
|
ret += [' %s = %s,' % (c.name, c.value)]
|
||
|
ret += ['};', '']
|
||
|
return '\n'.join(ret)
|
||
|
|
||
|
def GetMethodStubsString(self):
|
||
|
"""Returns the code corresponding to method stubs."""
|
||
|
ret = []
|
||
|
for native in self.natives:
|
||
|
ret += [self.GetNativeStub(native)]
|
||
|
ret += self.GetLazyCalledByNativeMethodStubs()
|
||
|
return '\n'.join(ret)
|
||
|
|
||
|
def GetLazyCalledByNativeMethodStubs(self):
|
||
|
return [self.GetLazyCalledByNativeMethodStub(called_by_native)
|
||
|
for called_by_native in self.called_by_natives]
|
||
|
|
||
|
def GetIncludesString(self):
|
||
|
if not self.options.includes:
|
||
|
return ''
|
||
|
includes = self.options.includes.split(',')
|
||
|
return '\n'.join('#include "%s"' % x for x in includes) + '\n'
|
||
|
|
||
|
def GetOpenNamespaceString(self):
|
||
|
if self.namespace:
|
||
|
all_namespaces = ['namespace %s {' % ns
|
||
|
for ns in self.namespace.split('::')]
|
||
|
return '\n'.join(all_namespaces) + '\n'
|
||
|
return ''
|
||
|
|
||
|
def GetCloseNamespaceString(self):
|
||
|
if self.namespace:
|
||
|
all_namespaces = ['} // namespace %s' % ns
|
||
|
for ns in self.namespace.split('::')]
|
||
|
all_namespaces.reverse()
|
||
|
return '\n' + '\n'.join(all_namespaces)
|
||
|
return ''
|
||
|
|
||
|
def GetCalledByNativeParamsInDeclaration(self, called_by_native):
|
||
|
return ',\n '.join([
|
||
|
JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' +
|
||
|
param.name
|
||
|
for param in called_by_native.params])
|
||
|
|
||
|
def GetJavaParamRefForCall(self, c_type, name):
|
||
|
return Template(
|
||
|
'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({
|
||
|
'TYPE': c_type,
|
||
|
'NAME': name,
|
||
|
})
|
||
|
|
||
|
def GetJNIFirstParamForCall(self, native):
|
||
|
c_type = _GetJNIFirstParamType(native)
|
||
|
return [self.GetJavaParamRefForCall(c_type, 'jcaller')]
|
||
|
|
||
|
def GetImplementationMethodName(self, native):
|
||
|
class_name = self.class_name
|
||
|
if native.java_class_name is not None:
|
||
|
# Inner class
|
||
|
class_name = native.java_class_name
|
||
|
return "JNI_%s_%s" % (class_name, native.name)
|
||
|
|
||
|
def GetNativeStub(self, native):
|
||
|
is_method = native.type == 'method'
|
||
|
|
||
|
if is_method:
|
||
|
params = native.params[1:]
|
||
|
else:
|
||
|
params = native.params
|
||
|
params_in_call = ['env'] + self.GetJNIFirstParamForCall(native)
|
||
|
for p in params:
|
||
|
c_type = JavaDataTypeToC(p.datatype)
|
||
|
if re.match(RE_SCOPED_JNI_TYPES, c_type):
|
||
|
params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name))
|
||
|
else:
|
||
|
params_in_call.append(p.name)
|
||
|
params_in_call = ', '.join(params_in_call)
|
||
|
|
||
|
return_type = return_declaration = JavaDataTypeToC(native.return_type)
|
||
|
post_call = ''
|
||
|
if re.match(RE_SCOPED_JNI_TYPES, return_type):
|
||
|
post_call = '.Release()'
|
||
|
return_declaration = ('base::android::ScopedJavaLocalRef<' + return_type +
|
||
|
'>')
|
||
|
profiling_entered_native = ''
|
||
|
if self.options.enable_profiling:
|
||
|
profiling_entered_native = ' JNI_LINK_SAVED_FRAME_POINTER;\n'
|
||
|
values = {
|
||
|
'RETURN': return_type,
|
||
|
'RETURN_DECLARATION': return_declaration,
|
||
|
'NAME': native.name,
|
||
|
'IMPL_METHOD_NAME': self.GetImplementationMethodName(native),
|
||
|
'PARAMS': _GetParamsInDeclaration(native),
|
||
|
'PARAMS_IN_STUB': GetParamsInStub(native),
|
||
|
'PARAMS_IN_CALL': params_in_call,
|
||
|
'POST_CALL': post_call,
|
||
|
'STUB_NAME': self.helper.GetStubName(native),
|
||
|
'PROFILING_ENTERED_NATIVE': profiling_entered_native,
|
||
|
'TRACE_EVENT': '',
|
||
|
}
|
||
|
|
||
|
namespace_qual = self.namespace + '::' if self.namespace else ''
|
||
|
if is_method:
|
||
|
optional_error_return = JavaReturnValueToC(native.return_type)
|
||
|
if optional_error_return:
|
||
|
optional_error_return = ', ' + optional_error_return
|
||
|
values.update({
|
||
|
'OPTIONAL_ERROR_RETURN': optional_error_return,
|
||
|
'PARAM0_NAME': native.params[0].name,
|
||
|
'P0_TYPE': native.p0_type,
|
||
|
})
|
||
|
if self.options.enable_tracing:
|
||
|
values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate(
|
||
|
namespace_qual + '${P0_TYPE}::${NAME}', values);
|
||
|
template = Template("""\
|
||
|
JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(
|
||
|
JNIEnv* env,
|
||
|
${PARAMS_IN_STUB}) {
|
||
|
${PROFILING_ENTERED_NATIVE}\
|
||
|
${TRACE_EVENT}\
|
||
|
${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME});
|
||
|
CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN});
|
||
|
return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL};
|
||
|
}
|
||
|
""")
|
||
|
else:
|
||
|
if self.options.enable_tracing:
|
||
|
values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate(
|
||
|
namespace_qual + '${IMPL_METHOD_NAME}', values)
|
||
|
template = Template("""\
|
||
|
static ${RETURN_DECLARATION} ${IMPL_METHOD_NAME}(JNIEnv* env, ${PARAMS});
|
||
|
|
||
|
JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(
|
||
|
JNIEnv* env,
|
||
|
${PARAMS_IN_STUB}) {
|
||
|
${PROFILING_ENTERED_NATIVE}\
|
||
|
${TRACE_EVENT}\
|
||
|
return ${IMPL_METHOD_NAME}(${PARAMS_IN_CALL})${POST_CALL};
|
||
|
}
|
||
|
""")
|
||
|
|
||
|
return RemoveIndentedEmptyLines(template.substitute(values))
|
||
|
|
||
|
def GetArgument(self, param):
|
||
|
if param.datatype == 'int':
|
||
|
return 'as_jint(' + param.name + ')'
|
||
|
elif re.match(RE_SCOPED_JNI_TYPES, JavaDataTypeToC(param.datatype)):
|
||
|
return param.name + '.obj()'
|
||
|
else:
|
||
|
return param.name
|
||
|
|
||
|
def GetArgumentsInCall(self, params):
|
||
|
"""Return a string of arguments to call from native into Java"""
|
||
|
return [self.GetArgument(p) for p in params]
|
||
|
|
||
|
def GetCalledByNativeValues(self, called_by_native):
|
||
|
"""Fills in necessary values for the CalledByNative methods."""
|
||
|
java_class_only = called_by_native.java_class_name or self.class_name
|
||
|
java_class = self.fully_qualified_class
|
||
|
if called_by_native.java_class_name:
|
||
|
java_class += '$' + called_by_native.java_class_name
|
||
|
|
||
|
if called_by_native.static or called_by_native.is_constructor:
|
||
|
first_param_in_declaration = ''
|
||
|
first_param_in_call = ('%s_clazz(env)' % GetBinaryClassName(java_class))
|
||
|
else:
|
||
|
first_param_in_declaration = (
|
||
|
', const base::android::JavaRef<jobject>& obj')
|
||
|
first_param_in_call = 'obj.obj()'
|
||
|
params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
|
||
|
called_by_native)
|
||
|
if params_in_declaration:
|
||
|
params_in_declaration = ', ' + params_in_declaration
|
||
|
params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params))
|
||
|
if params_in_call:
|
||
|
params_in_call = ', ' + params_in_call
|
||
|
pre_call = ''
|
||
|
post_call = ''
|
||
|
if called_by_native.static_cast:
|
||
|
pre_call = 'static_cast<%s>(' % called_by_native.static_cast
|
||
|
post_call = ')'
|
||
|
check_exception = ''
|
||
|
if not called_by_native.unchecked:
|
||
|
check_exception = 'jni_generator::CheckException(env);'
|
||
|
return_type = JavaDataTypeToC(called_by_native.return_type)
|
||
|
optional_error_return = JavaReturnValueToC(called_by_native.return_type)
|
||
|
if optional_error_return:
|
||
|
optional_error_return = ', ' + optional_error_return
|
||
|
return_declaration = ''
|
||
|
return_clause = ''
|
||
|
if return_type != 'void':
|
||
|
pre_call = ' ' + pre_call
|
||
|
return_declaration = return_type + ' ret ='
|
||
|
if re.match(RE_SCOPED_JNI_TYPES, return_type):
|
||
|
return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>'
|
||
|
return_clause = 'return ' + return_type + '(env, ret);'
|
||
|
else:
|
||
|
return_clause = 'return ret;'
|
||
|
profiling_leaving_native = ''
|
||
|
if self.options.enable_profiling:
|
||
|
profiling_leaving_native = ' JNI_SAVE_FRAME_POINTER;\n'
|
||
|
jni_name = called_by_native.name
|
||
|
jni_return_type = called_by_native.return_type
|
||
|
if called_by_native.is_constructor:
|
||
|
jni_name = '<init>'
|
||
|
jni_return_type = 'void'
|
||
|
if called_by_native.signature:
|
||
|
jni_signature = called_by_native.signature
|
||
|
else:
|
||
|
jni_signature = self.jni_params.Signature(
|
||
|
called_by_native.params, jni_return_type)
|
||
|
java_name_full = java_class.replace('/', '.') + '.' + jni_name
|
||
|
return {
|
||
|
'JAVA_CLASS_ONLY': java_class_only,
|
||
|
'JAVA_CLASS': GetBinaryClassName(java_class),
|
||
|
'RETURN_TYPE': return_type,
|
||
|
'OPTIONAL_ERROR_RETURN': optional_error_return,
|
||
|
'RETURN_DECLARATION': return_declaration,
|
||
|
'RETURN_CLAUSE': return_clause,
|
||
|
'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration,
|
||
|
'PARAMS_IN_DECLARATION': params_in_declaration,
|
||
|
'PRE_CALL': pre_call,
|
||
|
'POST_CALL': post_call,
|
||
|
'ENV_CALL': called_by_native.env_call,
|
||
|
'FIRST_PARAM_IN_CALL': first_param_in_call,
|
||
|
'PARAMS_IN_CALL': params_in_call,
|
||
|
'CHECK_EXCEPTION': check_exception,
|
||
|
'PROFILING_LEAVING_NATIVE': profiling_leaving_native,
|
||
|
'JNI_NAME': jni_name,
|
||
|
'JNI_SIGNATURE': jni_signature,
|
||
|
'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
|
||
|
'METHOD_ID_TYPE': 'STATIC' if called_by_native.static else 'INSTANCE',
|
||
|
'JAVA_NAME_FULL': java_name_full,
|
||
|
}
|
||
|
|
||
|
def GetLazyCalledByNativeMethodStub(self, called_by_native):
|
||
|
"""Returns a string."""
|
||
|
function_signature_template = Template("""\
|
||
|
static ${RETURN_TYPE} Java_${JAVA_CLASS_ONLY}_${METHOD_ID_VAR_NAME}(\
|
||
|
JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
|
||
|
function_header_template = Template("""\
|
||
|
${FUNCTION_SIGNATURE} {""")
|
||
|
function_header_with_unused_template = Template("""\
|
||
|
${FUNCTION_SIGNATURE} __attribute__ ((unused));
|
||
|
${FUNCTION_SIGNATURE} {""")
|
||
|
template = Template("""
|
||
|
static std::atomic<jmethodID> g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(nullptr);
|
||
|
${FUNCTION_HEADER}
|
||
|
CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
|
||
|
${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN});
|
||
|
jmethodID method_id = base::android::MethodID::LazyGet<
|
||
|
base::android::MethodID::TYPE_${METHOD_ID_TYPE}>(
|
||
|
env, ${JAVA_CLASS}_clazz(env),
|
||
|
"${JNI_NAME}",
|
||
|
${JNI_SIGNATURE},
|
||
|
&g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
|
||
|
|
||
|
${TRACE_EVENT}\
|
||
|
${PROFILING_LEAVING_NATIVE}\
|
||
|
${RETURN_DECLARATION}
|
||
|
${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
|
||
|
method_id${PARAMS_IN_CALL})${POST_CALL};
|
||
|
${CHECK_EXCEPTION}
|
||
|
${RETURN_CLAUSE}
|
||
|
}""")
|
||
|
values = self.GetCalledByNativeValues(called_by_native)
|
||
|
values['FUNCTION_SIGNATURE'] = (
|
||
|
function_signature_template.substitute(values))
|
||
|
if called_by_native.system_class:
|
||
|
values['FUNCTION_HEADER'] = (
|
||
|
function_header_with_unused_template.substitute(values))
|
||
|
else:
|
||
|
values['FUNCTION_HEADER'] = function_header_template.substitute(values)
|
||
|
if self.options.enable_tracing:
|
||
|
values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate(
|
||
|
'${JAVA_NAME_FULL}', values)
|
||
|
else:
|
||
|
values['TRACE_EVENT'] = ''
|
||
|
return RemoveIndentedEmptyLines(template.substitute(values))
|
||
|
|
||
|
def GetTraceEventForNameTemplate(self, name_template, values):
|
||
|
name = Template(name_template).substitute(values)
|
||
|
return ' TRACE_EVENT0("jni", "%s");' % name
|
||
|
|
||
|
|
||
|
def WrapOutput(output):
|
||
|
ret = []
|
||
|
for line in output.splitlines():
|
||
|
# Do not wrap preprocessor directives or comments.
|
||
|
if len(line) < _WRAP_LINE_LENGTH or line[0] == '#' or line.startswith('//'):
|
||
|
ret.append(line)
|
||
|
else:
|
||
|
# Assumes that the line is not already indented as a continuation line,
|
||
|
# which is not always true (oh well).
|
||
|
first_line_indent = (len(line) - len(line.lstrip()))
|
||
|
wrapper = _WRAPPERS_BY_INDENT[first_line_indent]
|
||
|
ret.extend(wrapper.wrap(line))
|
||
|
ret += ['']
|
||
|
return '\n'.join(ret)
|
||
|
|
||
|
|
||
|
def ExtractJarInputFile(jar_file, input_file, out_dir):
|
||
|
"""Extracts input file from jar and returns the filename.
|
||
|
|
||
|
The input file is extracted to the same directory that the generated jni
|
||
|
headers will be placed in. This is passed as an argument to script.
|
||
|
|
||
|
Args:
|
||
|
jar_file: the jar file containing the input files to extract.
|
||
|
input_files: the list of files to extract from the jar file.
|
||
|
out_dir: the name of the directories to extract to.
|
||
|
|
||
|
Returns:
|
||
|
the name of extracted input file.
|
||
|
"""
|
||
|
jar_file = zipfile.ZipFile(jar_file)
|
||
|
|
||
|
out_dir = os.path.join(out_dir, os.path.dirname(input_file))
|
||
|
try:
|
||
|
os.makedirs(out_dir)
|
||
|
except OSError as e:
|
||
|
if e.errno != errno.EEXIST:
|
||
|
raise
|
||
|
extracted_file_name = os.path.join(out_dir, os.path.basename(input_file))
|
||
|
with open(extracted_file_name, 'w') as outfile:
|
||
|
outfile.write(jar_file.read(input_file))
|
||
|
|
||
|
return extracted_file_name
|
||
|
|
||
|
|
||
|
def GenerateJNIHeader(input_file, output_file, options):
|
||
|
try:
|
||
|
if os.path.splitext(input_file)[1] == '.class':
|
||
|
jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options)
|
||
|
content = jni_from_javap.GetContent()
|
||
|
else:
|
||
|
jni_from_java_source = JNIFromJavaSource.CreateFromFile(
|
||
|
input_file, options)
|
||
|
content = jni_from_java_source.GetContent()
|
||
|
except ParseError, e:
|
||
|
print e
|
||
|
sys.exit(1)
|
||
|
if output_file:
|
||
|
WriteOutput(output_file, content)
|
||
|
else:
|
||
|
print content
|
||
|
|
||
|
|
||
|
def WriteOutput(output_file, content):
|
||
|
if os.path.exists(output_file):
|
||
|
with open(output_file) as f:
|
||
|
existing_content = f.read()
|
||
|
if existing_content == content:
|
||
|
return
|
||
|
with open(output_file, 'w') as f:
|
||
|
f.write(content)
|
||
|
|
||
|
|
||
|
def GetScriptName():
|
||
|
script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
|
||
|
base_index = 0
|
||
|
for idx, value in enumerate(script_components):
|
||
|
if value == 'base' or value == 'third_party':
|
||
|
base_index = idx
|
||
|
break
|
||
|
return os.sep.join(script_components[base_index:])
|
||
|
|
||
|
|
||
|
def main(argv):
|
||
|
usage = """usage: %prog [OPTIONS]
|
||
|
This script will parse the given java source code extracting the native
|
||
|
declarations and print the header file to stdout (or a file).
|
||
|
See SampleForTests.java for more details.
|
||
|
"""
|
||
|
option_parser = optparse.OptionParser(usage=usage)
|
||
|
|
||
|
option_parser.add_option('-j', '--jar_file', dest='jar_file',
|
||
|
help='Extract the list of input files from'
|
||
|
' a specified jar file.'
|
||
|
' Uses javap to extract the methods from a'
|
||
|
' pre-compiled class. --input should point'
|
||
|
' to pre-compiled Java .class files.')
|
||
|
option_parser.add_option('-n', dest='namespace',
|
||
|
help='Uses as a namespace in the generated header '
|
||
|
'instead of the javap class name, or when there is '
|
||
|
'no JNINamespace annotation in the java source.')
|
||
|
option_parser.add_option('--input_file',
|
||
|
help='Single input file name. The output file name '
|
||
|
'will be derived from it. Must be used with '
|
||
|
'--output_dir.')
|
||
|
option_parser.add_option('--output_dir',
|
||
|
help='The output directory. Must be used with '
|
||
|
'--input')
|
||
|
option_parser.add_option('--script_name', default=GetScriptName(),
|
||
|
help='The name of this script in the generated '
|
||
|
'header.')
|
||
|
option_parser.add_option('--includes',
|
||
|
help='The comma-separated list of header files to '
|
||
|
'include in the generated header.')
|
||
|
option_parser.add_option('--ptr_type', default='int',
|
||
|
type='choice', choices=['int', 'long'],
|
||
|
help='The type used to represent native pointers in '
|
||
|
'Java code. For 32-bit, use int; '
|
||
|
'for 64-bit, use long.')
|
||
|
option_parser.add_option('--cpp', default='cpp',
|
||
|
help='The path to cpp command.')
|
||
|
option_parser.add_option('--javap', default='javap',
|
||
|
help='The path to javap command.')
|
||
|
option_parser.add_option('--enable_profiling', action='store_true',
|
||
|
help='Add additional profiling instrumentation.')
|
||
|
option_parser.add_option('--enable_tracing', action='store_true',
|
||
|
help='Add TRACE_EVENTs to generated functions.')
|
||
|
options, args = option_parser.parse_args(argv)
|
||
|
if options.jar_file:
|
||
|
input_file = ExtractJarInputFile(options.jar_file, options.input_file,
|
||
|
options.output_dir)
|
||
|
elif options.input_file:
|
||
|
input_file = options.input_file
|
||
|
else:
|
||
|
option_parser.print_help()
|
||
|
print '\nError: Must specify --jar_file or --input_file.'
|
||
|
return 1
|
||
|
output_file = None
|
||
|
if options.output_dir:
|
||
|
root_name = os.path.splitext(os.path.basename(input_file))[0]
|
||
|
output_file = os.path.join(options.output_dir, root_name) + '_jni.h'
|
||
|
GenerateJNIHeader(input_file, output_file, options)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
sys.exit(main(sys.argv))
|