diff options
Diffstat (limited to 'chromium/base/android/jni_generator/jni_registration_generator.py')
-rwxr-xr-x | chromium/base/android/jni_generator/jni_registration_generator.py | 928 |
1 files changed, 928 insertions, 0 deletions
diff --git a/chromium/base/android/jni_generator/jni_registration_generator.py b/chromium/base/android/jni_generator/jni_registration_generator.py new file mode 100755 index 00000000000..f5563ed7b3b --- /dev/null +++ b/chromium/base/android/jni_generator/jni_registration_generator.py @@ -0,0 +1,928 @@ +#!/usr/bin/env python3 +# Copyright 2017 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generates GEN_JNI.java (or N.java) and helper for manual JNI registration. + +Creates a header file with two static functions: RegisterMainDexNatives() and +RegisterNonMainDexNatives(). Together, these will use manual JNI registration +to register all native methods that exist within an application.""" + +import argparse +import collections +import functools +import hashlib +import multiprocessing +import os +import string +import sys +import zipfile + +import jni_generator +from util import build_utils + +# All but FULL_CLASS_NAME, which is used only for sorting. +MERGEABLE_KEYS = [ + 'CLASS_PATH_DECLARATIONS', + 'FORWARD_DECLARATIONS', + 'JNI_NATIVE_METHOD', + 'JNI_NATIVE_METHOD_ARRAY', + 'PROXY_NATIVE_SIGNATURES', + 'FORWARDING_PROXY_METHODS', + 'PROXY_NATIVE_METHOD_ARRAY', + 'PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX', + 'REGISTER_MAIN_DEX_NATIVES', + 'REGISTER_NON_MAIN_DEX_NATIVES', +] + + +def _Generate(options, java_file_paths): + """Generates files required to perform JNI registration. + + Generates a srcjar containing a single class, GEN_JNI, that contains all + native method declarations. + + Optionally generates a header file that provides functions + (RegisterMainDexNatives and RegisterNonMainDexNatives) to perform + JNI registration. + + Args: + options: arguments from the command line + java_file_paths: A list of java file paths. + """ + # Without multiprocessing, script takes ~13 seconds for chrome_public_apk + # on a z620. With multiprocessing, takes ~2 seconds. + results = collections.defaultdict(list) + with multiprocessing.Pool() as pool: + for d in pool.imap_unordered(functools.partial(_DictForPath, options), + java_file_paths): + if d: + results[d['MODULE_NAME']].append(d) + + combined_dicts = collections.defaultdict(dict) + for module_name, module_results in results.items(): + # Sort to make output deterministic. + module_results.sort(key=lambda d: d['FULL_CLASS_NAME']) + combined_dict = combined_dicts[module_name] + for key in MERGEABLE_KEYS: + combined_dict[key] = ''.join(d.get(key, '') for d in module_results) + + # PROXY_NATIVE_SIGNATURES and PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX will have + # duplicates for JNI multiplexing since all native methods with similar + # signatures map to the same proxy. Similarly, there may be multiple switch + # case entries for the same proxy signatures. + if options.enable_jni_multiplexing: + proxy_signatures_list = sorted( + set(combined_dict['PROXY_NATIVE_SIGNATURES'].split('\n'))) + combined_dict['PROXY_NATIVE_SIGNATURES'] = '\n'.join( + signature for signature in proxy_signatures_list) + + proxy_native_array_list = sorted( + set(combined_dict['PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX'].split( + '},\n'))) + combined_dict['PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX'] = '},\n'.join( + p for p in proxy_native_array_list if p != '') + '}' + + signature_to_cases = collections.defaultdict(list) + for d in module_results: + for signature, cases in d['SIGNATURE_TO_CASES'].items(): + signature_to_cases[signature].extend(cases) + combined_dict['FORWARDING_CALLS'] = _AddForwardingCalls( + signature_to_cases, module_name) + + if options.header_path: + assert len( + combined_dicts) == 1, 'Cannot output a header for multiple modules' + module_name = next(iter(combined_dicts)) + combined_dict = combined_dicts[module_name] + + combined_dict['HEADER_GUARD'] = \ + os.path.splitext(options.header_path)[0].replace('/', '_').replace('.', '_').upper() + '_' + combined_dict['NAMESPACE'] = options.namespace + header_content = CreateFromDict(options, module_name, combined_dict) + with build_utils.AtomicOutput(options.header_path, mode='w') as f: + f.write(header_content) + + with build_utils.AtomicOutput(options.srcjar_path) as f: + with zipfile.ZipFile(f, 'w') as srcjar: + for module_name, combined_dict in combined_dicts.items(): + + if options.use_proxy_hash or options.enable_jni_multiplexing: + # J/N.java + build_utils.AddToZipHermetic( + srcjar, + '%s.java' % + jni_generator.ProxyHelpers.GetQualifiedClass(True, module_name), + data=CreateProxyJavaFromDict(options, module_name, combined_dict)) + # org/chromium/base/natives/GEN_JNI.java + build_utils.AddToZipHermetic( + srcjar, + '%s.java' % + jni_generator.ProxyHelpers.GetQualifiedClass(False, module_name), + data=CreateProxyJavaFromDict(options, + module_name, + combined_dict, + forwarding=True)) + else: + # org/chromium/base/natives/GEN_JNI.java + build_utils.AddToZipHermetic( + srcjar, + '%s.java' % + jni_generator.ProxyHelpers.GetQualifiedClass(False, module_name), + data=CreateProxyJavaFromDict(options, module_name, combined_dict)) + + +def _DictForPath(options, path): + with open(path) as f: + contents = jni_generator.RemoveComments(f.read()) + if '@JniIgnoreNatives' in contents: + return None + + fully_qualified_class = jni_generator.ExtractFullyQualifiedJavaClassName( + path, contents) + + natives, module_name = jni_generator.ProxyHelpers.ExtractStaticProxyNatives( + fully_qualified_class=fully_qualified_class, + contents=contents, + ptr_type='long', + include_test_only=options.include_test_only) + natives += jni_generator.ExtractNatives(contents, 'long') + + if len(natives) == 0: + return None + # The namespace for the content is separate from the namespace for the + # generated header file. + content_namespace = jni_generator.ExtractJNINamespace(contents) + jni_params = jni_generator.JniParams(fully_qualified_class) + jni_params.ExtractImportsAndInnerClasses(contents) + is_main_dex = jni_generator.IsMainDexJavaClass(contents) + dict_generator = DictionaryGenerator(options, module_name, content_namespace, + fully_qualified_class, natives, + jni_params, is_main_dex) + return dict_generator.Generate() + + +def _AddForwardingCalls(signature_to_cases, module_name): + template = string.Template(""" +JNI_GENERATOR_EXPORT ${RETURN} Java_${CLASS_NAME}_${PROXY_SIGNATURE}( + JNIEnv* env, + jclass jcaller, + ${PARAMS_IN_STUB}) { + switch (switch_num) { + ${CASES} + default: + CHECK(false) << "JNI multiplexing function Java_\ +${CLASS_NAME}_${PROXY_SIGNATURE} was called with an invalid switch number: "\ + << switch_num; + return${DEFAULT_RETURN}; + } +}""") + + switch_statements = [] + for signature, cases in sorted(signature_to_cases.items()): + return_type, params_list = signature + params_in_stub = _GetJavaToNativeParamsList(params_list) + switch_statements.append( + template.substitute({ + 'RETURN': + jni_generator.JavaDataTypeToC(return_type), + 'CLASS_NAME': + jni_generator.EscapeClassName( + jni_generator.ProxyHelpers.GetQualifiedClass(True, + module_name)), + 'PROXY_SIGNATURE': + jni_generator.EscapeClassName( + _GetMultiplexProxyName(return_type, params_list)), + 'PARAMS_IN_STUB': + params_in_stub, + 'CASES': + ''.join(cases), + 'DEFAULT_RETURN': + '' if return_type == 'void' else ' {}', + })) + + return ''.join(s for s in switch_statements) + + +def _SetProxyRegistrationFields(options, module_name, registration_dict): + registration_template = string.Template("""\ + +static const JNINativeMethod kMethods_${ESCAPED_PROXY_CLASS}[] = { +${KMETHODS} +}; + +namespace { + +JNI_REGISTRATION_EXPORT bool ${REGISTRATION_NAME}(JNIEnv* env) { + const int number_of_methods = std::size(kMethods_${ESCAPED_PROXY_CLASS}); + + base::android::ScopedJavaLocalRef<jclass> native_clazz = + base::android::GetClass(env, "${PROXY_CLASS}"); + if (env->RegisterNatives( + native_clazz.obj(), + kMethods_${ESCAPED_PROXY_CLASS}, + number_of_methods) < 0) { + + jni_generator::HandleRegistrationError(env, native_clazz.obj(), __FILE__); + return false; + } + + return true; +} + +} // namespace +""") + + registration_call = string.Template("""\ + + // Register natives in a proxy. + if (!${REGISTRATION_NAME}(env)) { + return false; + } +""") + + manual_registration = string.Template("""\ +// Step 3: Method declarations. + +${JNI_NATIVE_METHOD_ARRAY}\ +${PROXY_NATIVE_METHOD_ARRAY}\ + +${JNI_NATIVE_METHOD} +// Step 4: Main dex and non-main dex registration functions. + +namespace ${NAMESPACE} { + +bool RegisterMainDexNatives(JNIEnv* env) {\ +${REGISTER_MAIN_DEX_PROXY_NATIVES} +${REGISTER_MAIN_DEX_NATIVES} + return true; +} + +bool RegisterNonMainDexNatives(JNIEnv* env) {\ +${REGISTER_PROXY_NATIVES} +${REGISTER_NON_MAIN_DEX_NATIVES} + return true; +} + +} // namespace ${NAMESPACE} +""") + + short_name = options.use_proxy_hash or options.enable_jni_multiplexing + sub_dict = { + 'ESCAPED_PROXY_CLASS': + jni_generator.EscapeClassName( + jni_generator.ProxyHelpers.GetQualifiedClass(short_name, + module_name)), + 'PROXY_CLASS': + jni_generator.ProxyHelpers.GetQualifiedClass(short_name, module_name), + 'KMETHODS': + registration_dict['PROXY_NATIVE_METHOD_ARRAY'], + 'REGISTRATION_NAME': + jni_generator.GetRegistrationFunctionName( + jni_generator.ProxyHelpers.GetQualifiedClass(short_name, + module_name)), + } + + if registration_dict['PROXY_NATIVE_METHOD_ARRAY']: + proxy_native_array = registration_template.substitute(sub_dict) + proxy_natives_registration = registration_call.substitute(sub_dict) + else: + proxy_native_array = '' + proxy_natives_registration = '' + + if registration_dict['PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX']: + sub_dict['REGISTRATION_NAME'] += 'MAIN_DEX' + sub_dict['ESCAPED_PROXY_CLASS'] += 'MAIN_DEX' + sub_dict['KMETHODS'] = ( + registration_dict['PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX']) + proxy_native_array += registration_template.substitute(sub_dict) + main_dex_call = registration_call.substitute(sub_dict) + else: + main_dex_call = '' + + registration_dict['PROXY_NATIVE_METHOD_ARRAY'] = proxy_native_array + registration_dict['REGISTER_PROXY_NATIVES'] = proxy_natives_registration + registration_dict['REGISTER_MAIN_DEX_PROXY_NATIVES'] = main_dex_call + + if options.manual_jni_registration: + registration_dict['MANUAL_REGISTRATION'] = manual_registration.substitute( + registration_dict) + else: + registration_dict['MANUAL_REGISTRATION'] = '' + + +def CreateProxyJavaFromDict(options, + module_name, + registration_dict, + forwarding=False): + template = string.Template("""\ +// Copyright 2018 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package ${PACKAGE}; + +// This file is autogenerated by +// base/android/jni_generator/jni_registration_generator.py +// Please do not change its content. + +public class ${CLASS_NAME} { +${FIELDS} +${METHODS} +} +""") + + is_natives_class = not forwarding and (options.use_proxy_hash + or options.enable_jni_multiplexing) + class_name = jni_generator.ProxyHelpers.GetClass(is_natives_class, + module_name) + package = jni_generator.ProxyHelpers.GetPackage(is_natives_class) + + if forwarding or not (options.use_proxy_hash + or options.enable_jni_multiplexing): + fields = string.Template("""\ + public static final boolean TESTING_ENABLED = ${TESTING_ENABLED}; + public static final boolean REQUIRE_MOCK = ${REQUIRE_MOCK}; +""").substitute({ + 'TESTING_ENABLED': str(options.enable_proxy_mocks).lower(), + 'REQUIRE_MOCK': str(options.require_mocks).lower(), + }) + else: + fields = '' + + if forwarding: + methods = registration_dict['FORWARDING_PROXY_METHODS'] + else: + methods = registration_dict['PROXY_NATIVE_SIGNATURES'] + + return template.substitute({ + 'CLASS_NAME': class_name, + 'FIELDS': fields, + 'PACKAGE': package.replace('/', '.'), + 'METHODS': methods + }) + + +def CreateFromDict(options, module_name, registration_dict): + """Returns the content of the header file.""" + + template = string.Template("""\ +// Copyright 2017 The Chromium Authors +// 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 +// base/android/jni_generator/jni_registration_generator.py +// Please do not change its content. + +#ifndef ${HEADER_GUARD} +#define ${HEADER_GUARD} + +#include <jni.h> + +#include <iterator> + +#include "base/android/jni_generator/jni_generator_helper.h" +#include "base/android/jni_int_wrapper.h" + + +// Step 1: Forward declarations (classes). +${CLASS_PATH_DECLARATIONS} + +// Step 2: Forward declarations (methods). + +${FORWARD_DECLARATIONS} +${FORWARDING_CALLS} +${MANUAL_REGISTRATION} +#endif // ${HEADER_GUARD} +""") + _SetProxyRegistrationFields(options, module_name, registration_dict) + if not options.enable_jni_multiplexing: + registration_dict['FORWARDING_CALLS'] = '' + if len(registration_dict['FORWARD_DECLARATIONS']) == 0: + return '' + + return template.substitute(registration_dict) + + +def _GetJavaToNativeParamsList(params_list): + if not params_list: + return 'jlong switch_num' + + # Parameters are named after their type, with a unique number per parameter + # type to make sure the names are unique, even within the same types. + params_type_count = collections.defaultdict(int) + params_in_stub = [] + for p in params_list: + params_type_count[p] += 1 + params_in_stub.append( + '%s %s_param%d' % + (jni_generator.JavaDataTypeToC(p), p.replace( + '[]', '_array').lower(), params_type_count[p])) + + return 'jlong switch_num, ' + ', '.join(params_in_stub) + + +class DictionaryGenerator(object): + """Generates an inline header file for JNI registration.""" + + def __init__(self, options, module_name, content_namespace, + fully_qualified_class, natives, jni_params, main_dex): + self.options = options + self.module_name = module_name + self.content_namespace = content_namespace + self.natives = natives + self.proxy_natives = [n for n in natives if n.is_proxy] + self.non_proxy_natives = [n for n in natives if not n.is_proxy] + self.fully_qualified_class = fully_qualified_class + self.jni_params = jni_params + self.class_name = self.fully_qualified_class.split('/')[-1] + self.main_dex = main_dex + self.helper = jni_generator.HeaderFileGeneratorHelper( + self.class_name, + self.module_name, + fully_qualified_class, + options.use_proxy_hash, + enable_jni_multiplexing=options.enable_jni_multiplexing) + self.registration_dict = None + + def Generate(self): + self.registration_dict = { + 'FULL_CLASS_NAME': self.fully_qualified_class, + 'MODULE_NAME': self.module_name + } + self._AddClassPathDeclarations() + self._AddForwardDeclaration() + self._AddJNINativeMethodsArrays() + self._AddProxyNativeMethodKStrings() + self._AddRegisterNativesCalls() + self._AddRegisterNativesFunctions() + + self.registration_dict['PROXY_NATIVE_SIGNATURES'] = (''.join( + _MakeProxySignature(self.options, native) + for native in self.proxy_natives)) + + if self.options.enable_jni_multiplexing: + self._AssignSwitchNumberToNatives() + self._AddCases() + + if self.options.use_proxy_hash or self.options.enable_jni_multiplexing: + self.registration_dict['FORWARDING_PROXY_METHODS'] = ('\n'.join( + _MakeForwardingProxy(self.options, self.module_name, native) + for native in self.proxy_natives)) + + return self.registration_dict + + def _SetDictValue(self, key, value): + self.registration_dict[key] = jni_generator.WrapOutput(value) + + def _AddClassPathDeclarations(self): + classes = self.helper.GetUniqueClasses(self.natives) + self._SetDictValue( + 'CLASS_PATH_DECLARATIONS', + self.helper.GetClassPathLines(classes, declare_only=True)) + + def _AddForwardDeclaration(self): + """Add the content of the forward declaration to the dictionary.""" + template = string.Template("""\ +JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}( + JNIEnv* env, + ${PARAMS_IN_STUB}); +""") + forward_declaration = '' + for native in self.natives: + value = { + 'RETURN': jni_generator.JavaDataTypeToC(native.return_type), + 'STUB_NAME': self.helper.GetStubName(native), + 'PARAMS_IN_STUB': jni_generator.GetParamsInStub(native), + } + forward_declaration += template.substitute(value) + self._SetDictValue('FORWARD_DECLARATIONS', forward_declaration) + + def _AddRegisterNativesCalls(self): + """Add the body of the RegisterNativesImpl method to the dictionary.""" + + # Only register if there is at least 1 non-proxy native + if len(self.non_proxy_natives) == 0: + return '' + + template = string.Template("""\ + if (!${REGISTER_NAME}(env)) + return false; +""") + value = { + 'REGISTER_NAME': + jni_generator.GetRegistrationFunctionName(self.fully_qualified_class) + } + register_body = template.substitute(value) + if self.main_dex: + self._SetDictValue('REGISTER_MAIN_DEX_NATIVES', register_body) + else: + self._SetDictValue('REGISTER_NON_MAIN_DEX_NATIVES', register_body) + + def _AddJNINativeMethodsArrays(self): + """Returns the implementation of the array of native methods.""" + template = string.Template("""\ +static const JNINativeMethod kMethods_${JAVA_CLASS}[] = { +${KMETHODS} +}; + +""") + open_namespace = '' + close_namespace = '' + if self.content_namespace: + parts = self.content_namespace.split('::') + all_namespaces = ['namespace %s {' % ns for ns in parts] + open_namespace = '\n'.join(all_namespaces) + '\n' + all_namespaces = ['} // namespace %s' % ns for ns in parts] + all_namespaces.reverse() + close_namespace = '\n'.join(all_namespaces) + '\n\n' + + body = self._SubstituteNativeMethods(template) + if body: + self._SetDictValue('JNI_NATIVE_METHOD_ARRAY', ''.join( + (open_namespace, body, close_namespace))) + + def _GetKMethodsString(self, clazz): + ret = [] + for native in self.non_proxy_natives: + if (native.java_class_name == clazz + or (not native.java_class_name and clazz == self.class_name)): + ret += [self._GetKMethodArrayEntry(native)] + return '\n'.join(ret) + + def _GetKMethodArrayEntry(self, native): + template = string.Template(' { "${NAME}", ${JNI_SIGNATURE}, ' + + 'reinterpret_cast<void*>(${STUB_NAME}) },') + + name = 'native' + native.name + jni_signature = self.jni_params.Signature(native.params, native.return_type) + stub_name = self.helper.GetStubName(native) + + if native.is_proxy: + # Literal name of the native method in the class that contains the actual + # native declaration. + if self.options.enable_jni_multiplexing: + return_type, params_list = native.return_and_signature + class_name = jni_generator.EscapeClassName( + jni_generator.ProxyHelpers.GetQualifiedClass( + True, self.module_name)) + proxy_signature = jni_generator.EscapeClassName( + _GetMultiplexProxyName(return_type, params_list)) + + name = _GetMultiplexProxyName(return_type, params_list) + jni_signature = self.jni_params.Signature( + [jni_generator.Param(datatype='long', name='switch_num')] + + native.params, native.return_type) + stub_name = 'Java_' + class_name + '_' + proxy_signature + elif self.options.use_proxy_hash: + name = native.hashed_proxy_name + else: + name = native.proxy_name + values = { + 'NAME': name, + 'JNI_SIGNATURE': jni_signature, + 'STUB_NAME': stub_name + } + return template.substitute(values) + + def _AddProxyNativeMethodKStrings(self): + """Returns KMethodString for wrapped native methods in all_classes """ + + if self.main_dex or self.options.enable_jni_multiplexing: + key = 'PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX' + else: + key = 'PROXY_NATIVE_METHOD_ARRAY' + + proxy_k_strings = ('\n'.join( + self._GetKMethodArrayEntry(p) for p in self.proxy_natives)) + + self._SetDictValue(key, proxy_k_strings) + + def _SubstituteNativeMethods(self, template): + """Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided + template.""" + ret = [] + all_classes = self.helper.GetUniqueClasses(self.natives) + all_classes[self.class_name] = self.fully_qualified_class + + for clazz, full_clazz in all_classes.items(): + if clazz == jni_generator.ProxyHelpers.GetClass( + self.options.use_proxy_hash or self.options.enable_jni_multiplexing, + self.module_name): + continue + + kmethods = self._GetKMethodsString(clazz) + namespace_str = '' + if self.content_namespace: + namespace_str = self.content_namespace + '::' + if kmethods: + values = { + 'NAMESPACE': namespace_str, + 'JAVA_CLASS': jni_generator.EscapeClassName(full_clazz), + 'KMETHODS': kmethods + } + ret += [template.substitute(values)] + if not ret: return '' + return '\n'.join(ret) + + def GetJNINativeMethodsString(self): + """Returns the implementation of the array of native methods.""" + template = string.Template("""\ +static const JNINativeMethod kMethods_${JAVA_CLASS}[] = { +${KMETHODS} + +}; +""") + return self._SubstituteNativeMethods(template) + + def _AddRegisterNativesFunctions(self): + """Returns the code for RegisterNatives.""" + natives = self._GetRegisterNativesImplString() + if not natives: + return '' + template = string.Template("""\ +JNI_REGISTRATION_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) { +${NATIVES}\ + return true; +} + +""") + values = { + 'REGISTER_NAME': + jni_generator.GetRegistrationFunctionName(self.fully_qualified_class), + 'NATIVES': + natives + } + self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values)) + + def _GetRegisterNativesImplString(self): + """Returns the shared implementation for RegisterNatives.""" + template = string.Template("""\ + const int kMethods_${JAVA_CLASS}Size = + std::size(${NAMESPACE}kMethods_${JAVA_CLASS}); + if (env->RegisterNatives( + ${JAVA_CLASS}_clazz(env), + ${NAMESPACE}kMethods_${JAVA_CLASS}, + kMethods_${JAVA_CLASS}Size) < 0) { + jni_generator::HandleRegistrationError(env, + ${JAVA_CLASS}_clazz(env), + __FILE__); + return false; + } + +""") + # Only register if there is a native method not in a proxy, + # since all the proxies will be registered together. + if len(self.non_proxy_natives) != 0: + return self._SubstituteNativeMethods(template) + return '' + + def _AssignSwitchNumberToNatives(self): + # The switch number for a native method is a 64-bit long with the first + # bit being a sign digit. The signed two's complement is taken when + # appropriate to make use of negative numbers. + for native in self.proxy_natives: + hashed_long = hashlib.md5( + native.proxy_name.encode('utf-8')).hexdigest()[:16] + switch_num = int(hashed_long, 16) + if (switch_num & 1 << 63): + switch_num -= (1 << 64) + + native.switch_num = str(switch_num) + + def _AddCases(self): + # Switch cases are grouped together by the same proxy signatures. + template = string.Template(""" + case ${SWITCH_NUM}: + return ${STUB_NAME}(env, jcaller${PARAMS}); + """) + + signature_to_cases = collections.defaultdict(list) + for native in self.proxy_natives: + signature = native.return_and_signature + params = _GetParamsListForMultiplex(signature[1], with_types=False) + values = { + 'SWITCH_NUM': native.switch_num, + # We are forced to call the generated stub instead of the impl because + # the impl is not guaranteed to have a globally unique name. + 'STUB_NAME': self.helper.GetStubName(native), + 'PARAMS': params, + } + signature_to_cases[signature].append(template.substitute(values)) + + self.registration_dict['SIGNATURE_TO_CASES'] = signature_to_cases + + +def _GetParamsListForMultiplex(params_list, with_types): + if not params_list: + return '' + + # Parameters are named after their type, with a unique number per parameter + # type to make sure the names are unique, even within the same types. + params_type_count = collections.defaultdict(int) + params = [] + for p in params_list: + params_type_count[p] += 1 + param_type = p + ' ' if with_types else '' + params.append( + '%s%s_param%d' % + (param_type, p.replace('[]', '_array').lower(), params_type_count[p])) + + return ', ' + ', '.join(params) + + +def _GetMultiplexProxyName(return_type, params_list): + # Proxy signatures for methods are named after their return type and + # parameters to ensure uniqueness, even for the same return types. + params = '' + if params_list: + type_convert_dictionary = { + '[]': 'A', + 'byte': 'B', + 'char': 'C', + 'double': 'D', + 'float': 'F', + 'int': 'I', + 'long': 'J', + 'Class': 'L', + 'Object': 'O', + 'String': 'R', + 'short': 'S', + 'Throwable': 'T', + 'boolean': 'Z', + } + # Parameter types could contain multi-dimensional arrays and every + # instance of [] has to be replaced in the proxy signature name. + for k, v in type_convert_dictionary.items(): + params_list = [p.replace(k, v) for p in params_list] + params = '_' + ''.join(p for p in params_list) + + return 'resolve_for_' + return_type.replace('[]', '_array').lower() + params + + +def _MakeForwardingProxy(options, module_name, proxy_native): + template = string.Template(""" + public static ${RETURN_TYPE} ${METHOD_NAME}(${PARAMS_WITH_TYPES}) { + ${MAYBE_RETURN}${PROXY_CLASS}.${PROXY_METHOD_NAME}(${PARAM_NAMES}); + }""") + + params_with_types = ', '.join( + '%s %s' % (p.datatype, p.name) for p in proxy_native.params) + param_names = ', '.join(p.name for p in proxy_native.params) + proxy_class = jni_generator.ProxyHelpers.GetQualifiedClass(True, module_name) + + if options.enable_jni_multiplexing: + if not param_names: + param_names = proxy_native.switch_num + 'L' + else: + param_names = proxy_native.switch_num + 'L, ' + param_names + return_type, params_list = proxy_native.return_and_signature + proxy_method_name = _GetMultiplexProxyName(return_type, params_list) + else: + proxy_method_name = proxy_native.hashed_proxy_name + + return template.substitute({ + 'RETURN_TYPE': + proxy_native.return_type, + 'METHOD_NAME': + proxy_native.proxy_name, + 'PARAMS_WITH_TYPES': + params_with_types, + 'MAYBE_RETURN': + '' if proxy_native.return_type == 'void' else 'return ', + 'PROXY_CLASS': + proxy_class.replace('/', '.'), + 'PROXY_METHOD_NAME': + proxy_method_name, + 'PARAM_NAMES': + param_names, + }) + + +def _MakeProxySignature(options, proxy_native): + params_with_types = ', '.join('%s %s' % (p.datatype, p.name) + for p in proxy_native.params) + native_method_line = """ + public static native ${RETURN} ${PROXY_NAME}(${PARAMS_WITH_TYPES});""" + + if options.enable_jni_multiplexing: + # This has to be only one line and without comments because all the proxy + # signatures will be joined, then split on new lines with duplicates removed + # since multiple |proxy_native|s map to the same multiplexed signature. + signature_template = string.Template(native_method_line) + + alt_name = None + return_type, params_list = proxy_native.return_and_signature + proxy_name = _GetMultiplexProxyName(return_type, params_list) + params_with_types = 'long switch_num' + _GetParamsListForMultiplex( + params_list, with_types=True) + elif options.use_proxy_hash: + signature_template = string.Template(""" + // Original name: ${ALT_NAME}""" + native_method_line) + + alt_name = proxy_native.proxy_name + proxy_name = proxy_native.hashed_proxy_name + else: + signature_template = string.Template(""" + // Hashed name: ${ALT_NAME}""" + native_method_line) + + # We add the prefix that is sometimes used so that codesearch can find it if + # someone searches a full method name from the stacktrace. + alt_name = f'Java_J_N_{proxy_native.hashed_proxy_name}' + proxy_name = proxy_native.proxy_name + + return signature_template.substitute({ + 'ALT_NAME': alt_name, + 'RETURN': proxy_native.return_type, + 'PROXY_NAME': proxy_name, + 'PARAMS_WITH_TYPES': params_with_types, + }) + + +def main(argv): + arg_parser = argparse.ArgumentParser() + build_utils.AddDepfileOption(arg_parser) + + arg_parser.add_argument( + '--sources-files', + required=True, + action='append', + help='A list of .sources files which contain Java ' + 'file paths.') + arg_parser.add_argument( + '--header-path', help='Path to output header file (optional).') + arg_parser.add_argument( + '--srcjar-path', + required=True, + help='Path to output srcjar for GEN_JNI.java (and J/N.java if proxy' + ' hash is enabled).') + arg_parser.add_argument('--file-exclusions', + default=[], + help='A list of Java files which should be ignored ' + 'by the parser.') + arg_parser.add_argument( + '--namespace', + default='', + help='Native namespace to wrap the registration functions ' + 'into.') + # TODO(crbug.com/898261) hook these flags up to the build config to enable + # mocking in instrumentation tests + arg_parser.add_argument( + '--enable-proxy-mocks', + default=False, + action='store_true', + help='Allows proxy native impls to be mocked through Java.') + arg_parser.add_argument( + '--require-mocks', + default=False, + action='store_true', + help='Requires all used native implementations to have a mock set when ' + 'called. Otherwise an exception will be thrown.') + arg_parser.add_argument( + '--use-proxy-hash', + action='store_true', + help='Enables hashing of the native declaration for methods in ' + 'an @JniNatives interface') + arg_parser.add_argument( + '--enable-jni-multiplexing', + action='store_true', + help='Enables JNI multiplexing for Java native methods') + arg_parser.add_argument( + '--manual-jni-registration', + action='store_true', + help='Manually do JNI registration - required for crazy linker') + arg_parser.add_argument('--include-test-only', + action='store_true', + help='Whether to maintain ForTesting JNI methods.') + args = arg_parser.parse_args(build_utils.ExpandFileArgs(argv[1:])) + + if not args.enable_proxy_mocks and args.require_mocks: + arg_parser.error( + 'Invalid arguments: --require-mocks without --enable-proxy-mocks. ' + 'Cannot require mocks if they are not enabled.') + if not args.header_path and args.manual_jni_registration: + arg_parser.error( + 'Invalid arguments: --manual-jni-registration without --header-path. ' + 'Cannot manually register JNI if there is no output header file.') + + sources_files = sorted(set(build_utils.ParseGnList(args.sources_files))) + + java_file_paths = [] + for f in sources_files: + # Skip generated files, since the GN targets do not declare any deps. Also + # skip Kotlin files as they are not supported by JNI generation. + java_file_paths.extend( + p for p in build_utils.ReadSourcesList(f) if p.startswith('..') + and p not in args.file_exclusions and not p.endswith('.kt')) + _Generate(args, java_file_paths) + + if args.depfile: + build_utils.WriteDepfile(args.depfile, args.srcjar_path, + sources_files + java_file_paths) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) |