1#!/usr/bin/env python3 2# Copyright 2017 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Generates GEN_JNI.java (or N.java) and optional header for manual JNI 7registration. 8""" 9 10import argparse 11import collections 12import functools 13import hashlib 14import multiprocessing 15import os 16import re 17import string 18import sys 19import zipfile 20 21import jni_generator 22from util import build_utils 23import action_helpers # build_utils adds //build to sys.path. 24import zip_helpers 25 26# All but FULL_CLASS_NAME, which is used only for sorting. 27MERGEABLE_KEYS = [ 28 'CLASS_PATH_DECLARATIONS', 29 'FORWARD_DECLARATIONS', 30 'JNI_NATIVE_METHOD', 31 'JNI_NATIVE_METHOD_ARRAY', 32 'PROXY_NATIVE_SIGNATURES', 33 'FORWARDING_PROXY_METHODS', 34 'PROXY_NATIVE_METHOD_ARRAY', 35 'REGISTER_NATIVES', 36] 37 38 39def _Generate(options, java_file_paths): 40 """Generates files required to perform JNI registration. 41 42 Generates a srcjar containing a single class, GEN_JNI, that contains all 43 native method declarations. 44 45 Optionally generates a header file that provides RegisterNatives to perform 46 JNI registration. 47 48 Args: 49 options: arguments from the command line 50 java_file_paths: A list of java file paths. 51 """ 52 # Without multiprocessing, script takes ~13 seconds for chrome_public_apk 53 # on a z620. With multiprocessing, takes ~2 seconds. 54 results = collections.defaultdict(list) 55 with multiprocessing.Pool() as pool: 56 for d in pool.imap_unordered(functools.partial(_DictForPath, options), 57 java_file_paths): 58 if d: 59 results[d['MODULE_NAME']].append(d) 60 61 combined_dicts = collections.defaultdict(dict) 62 for module_name, module_results in results.items(): 63 # Sort to make output deterministic. 64 module_results.sort(key=lambda d: d['FULL_CLASS_NAME']) 65 combined_dict = combined_dicts[module_name] 66 for key in MERGEABLE_KEYS: 67 combined_dict[key] = ''.join(d.get(key, '') for d in module_results) 68 69 # PROXY_NATIVE_SIGNATURES and PROXY_NATIVE_METHOD_ARRAY will have 70 # duplicates for JNI multiplexing since all native methods with similar 71 # signatures map to the same proxy. Similarly, there may be multiple switch 72 # case entries for the same proxy signatures. 73 if options.enable_jni_multiplexing: 74 proxy_signatures_list = sorted( 75 set(combined_dict['PROXY_NATIVE_SIGNATURES'].split('\n'))) 76 combined_dict['PROXY_NATIVE_SIGNATURES'] = '\n'.join( 77 signature for signature in proxy_signatures_list) 78 79 proxy_native_array_list = sorted( 80 set(combined_dict['PROXY_NATIVE_METHOD_ARRAY'].split('},\n'))) 81 combined_dict['PROXY_NATIVE_METHOD_ARRAY'] = '},\n'.join( 82 p for p in proxy_native_array_list if p != '') + '}' 83 84 signature_to_cases = collections.defaultdict(list) 85 for d in module_results: 86 for signature, cases in d['SIGNATURE_TO_CASES'].items(): 87 signature_to_cases[signature].extend(cases) 88 combined_dict['FORWARDING_CALLS'] = _AddForwardingCalls( 89 signature_to_cases, module_name, options.package_prefix) 90 91 if options.header_path: 92 assert len( 93 combined_dicts) == 1, 'Cannot output a header for multiple modules' 94 module_name = next(iter(combined_dicts)) 95 combined_dict = combined_dicts[module_name] 96 97 header_guard = os.path.splitext(options.header_path)[0].upper() + '_' 98 header_guard = re.sub(r'[/.-]', '_', header_guard) 99 combined_dict['HEADER_GUARD'] = header_guard 100 combined_dict['NAMESPACE'] = options.namespace 101 header_content = CreateFromDict(options, module_name, combined_dict) 102 with action_helpers.atomic_output(options.header_path, mode='w') as f: 103 f.write(header_content) 104 105 with action_helpers.atomic_output(options.srcjar_path) as f: 106 with zipfile.ZipFile(f, 'w') as srcjar: 107 for module_name, combined_dict in combined_dicts.items(): 108 109 if options.use_proxy_hash or options.enable_jni_multiplexing: 110 # J/N.java 111 zip_helpers.add_to_zip_hermetic( 112 srcjar, 113 '%s.java' % jni_generator.ProxyHelpers.GetQualifiedClass( 114 True, module_name, options.package_prefix), 115 data=CreateProxyJavaFromDict(options, module_name, combined_dict)) 116 # org/chromium/base/natives/GEN_JNI.java 117 zip_helpers.add_to_zip_hermetic( 118 srcjar, 119 '%s.java' % jni_generator.ProxyHelpers.GetQualifiedClass( 120 False, module_name, options.package_prefix), 121 data=CreateProxyJavaFromDict(options, 122 module_name, 123 combined_dict, 124 forwarding=True)) 125 else: 126 # org/chromium/base/natives/GEN_JNI.java 127 zip_helpers.add_to_zip_hermetic( 128 srcjar, 129 '%s.java' % jni_generator.ProxyHelpers.GetQualifiedClass( 130 False, module_name, options.package_prefix), 131 data=CreateProxyJavaFromDict(options, module_name, combined_dict)) 132 133 134def _DictForPath(options, path): 135 with open(path) as f: 136 contents = jni_generator.RemoveComments(f.read()) 137 if '@JniIgnoreNatives' in contents: 138 return None 139 140 fully_qualified_class = jni_generator.ExtractFullyQualifiedJavaClassName( 141 path, contents) 142 143 if options.package_prefix: 144 fully_qualified_class = jni_generator.GetFullyQualifiedClassWithPackagePrefix( 145 fully_qualified_class, options.package_prefix) 146 147 natives, found_module_name = jni_generator.ProxyHelpers.ExtractStaticProxyNatives( 148 fully_qualified_class=fully_qualified_class, 149 contents=contents, 150 ptr_type='long', 151 include_test_only=options.include_test_only) 152 153 if options.module_name and found_module_name != options.module_name: 154 # Ignoring any code from modules we aren't looking at. 155 return None 156 157 natives += jni_generator.ExtractNatives(contents, 'long') 158 159 if len(natives) == 0: 160 return None 161 # The namespace for the content is separate from the namespace for the 162 # generated header file. 163 content_namespace = jni_generator.ExtractJNINamespace(contents) 164 jni_params = jni_generator.JniParams(fully_qualified_class) 165 jni_params.ExtractImportsAndInnerClasses(contents) 166 dict_generator = DictionaryGenerator(options, found_module_name, 167 content_namespace, fully_qualified_class, 168 natives, jni_params) 169 return dict_generator.Generate() 170 171 172def _AddForwardingCalls(signature_to_cases, module_name, package_prefix): 173 template = string.Template(""" 174JNI_GENERATOR_EXPORT ${RETURN} Java_${CLASS_NAME}_${PROXY_SIGNATURE}( 175 JNIEnv* env, 176 jclass jcaller, 177 ${PARAMS_IN_STUB}) { 178 switch (switch_num) { 179 ${CASES} 180 default: 181 CHECK(false) << "JNI multiplexing function Java_\ 182${CLASS_NAME}_${PROXY_SIGNATURE} was called with an invalid switch number: "\ 183 << switch_num; 184 return${DEFAULT_RETURN}; 185 } 186}""") 187 188 switch_statements = [] 189 for signature, cases in sorted(signature_to_cases.items()): 190 return_type, params_list = signature 191 params_in_stub = _GetJavaToNativeParamsList(params_list) 192 switch_statements.append( 193 template.substitute({ 194 'RETURN': 195 jni_generator.JavaDataTypeToC(return_type), 196 'CLASS_NAME': 197 jni_generator.EscapeClassName( 198 jni_generator.ProxyHelpers.GetQualifiedClass( 199 True, module_name, package_prefix)), 200 'PROXY_SIGNATURE': 201 jni_generator.EscapeClassName( 202 _GetMultiplexProxyName(return_type, params_list)), 203 'PARAMS_IN_STUB': 204 params_in_stub, 205 'CASES': 206 ''.join(cases), 207 'DEFAULT_RETURN': 208 '' if return_type == 'void' else ' {}', 209 })) 210 211 return ''.join(s for s in switch_statements) 212 213 214def _SetProxyRegistrationFields(options, module_name, registration_dict): 215 registration_template = string.Template("""\ 216 217static const JNINativeMethod kMethods_${ESCAPED_PROXY_CLASS}[] = { 218${KMETHODS} 219}; 220 221namespace { 222 223JNI_REGISTRATION_EXPORT bool ${REGISTRATION_NAME}(JNIEnv* env) { 224 const int number_of_methods = std::size(kMethods_${ESCAPED_PROXY_CLASS}); 225 226 base::android::ScopedJavaLocalRef<jclass> native_clazz = 227 base::android::GetClass(env, "${PROXY_CLASS}"); 228 if (env->RegisterNatives( 229 native_clazz.obj(), 230 kMethods_${ESCAPED_PROXY_CLASS}, 231 number_of_methods) < 0) { 232 233 jni_generator::HandleRegistrationError(env, native_clazz.obj(), __FILE__); 234 return false; 235 } 236 237 return true; 238} 239 240} // namespace 241""") 242 243 registration_call = string.Template("""\ 244 245 // Register natives in a proxy. 246 if (!${REGISTRATION_NAME}(env)) { 247 return false; 248 } 249""") 250 251 manual_registration = string.Template("""\ 252// Step 3: Method declarations. 253 254${JNI_NATIVE_METHOD_ARRAY}\ 255${PROXY_NATIVE_METHOD_ARRAY}\ 256 257${JNI_NATIVE_METHOD} 258// Step 4: Registration function. 259 260namespace ${NAMESPACE} { 261 262bool RegisterNatives(JNIEnv* env) {\ 263${REGISTER_PROXY_NATIVES} 264${REGISTER_NATIVES} 265 return true; 266} 267 268} // namespace ${NAMESPACE} 269""") 270 271 short_name = options.use_proxy_hash or options.enable_jni_multiplexing 272 sub_dict = { 273 'ESCAPED_PROXY_CLASS': 274 jni_generator.EscapeClassName( 275 jni_generator.ProxyHelpers.GetQualifiedClass(short_name, module_name, 276 options.package_prefix)), 277 'PROXY_CLASS': 278 jni_generator.ProxyHelpers.GetQualifiedClass(short_name, module_name, 279 options.package_prefix), 280 'KMETHODS': 281 registration_dict['PROXY_NATIVE_METHOD_ARRAY'], 282 'REGISTRATION_NAME': 283 jni_generator.GetRegistrationFunctionName( 284 jni_generator.ProxyHelpers.GetQualifiedClass(short_name, module_name, 285 options.package_prefix)), 286 } 287 288 if registration_dict['PROXY_NATIVE_METHOD_ARRAY']: 289 proxy_native_array = registration_template.substitute(sub_dict) 290 proxy_natives_registration = registration_call.substitute(sub_dict) 291 else: 292 proxy_native_array = '' 293 proxy_natives_registration = '' 294 295 registration_dict['PROXY_NATIVE_METHOD_ARRAY'] = proxy_native_array 296 registration_dict['REGISTER_PROXY_NATIVES'] = proxy_natives_registration 297 298 if options.manual_jni_registration: 299 registration_dict['MANUAL_REGISTRATION'] = manual_registration.substitute( 300 registration_dict) 301 else: 302 registration_dict['MANUAL_REGISTRATION'] = '' 303 304 305def CreateProxyJavaFromDict(options, 306 module_name, 307 registration_dict, 308 forwarding=False): 309 template = string.Template("""\ 310// Copyright 2018 The Chromium Authors 311// Use of this source code is governed by a BSD-style license that can be 312// found in the LICENSE file. 313 314package ${PACKAGE}; 315 316// This file is autogenerated by 317// base/android/jni_generator/jni_registration_generator.py 318// Please do not change its content. 319 320public class ${CLASS_NAME} { 321${FIELDS} 322${METHODS} 323} 324""") 325 326 is_natives_class = not forwarding and (options.use_proxy_hash 327 or options.enable_jni_multiplexing) 328 class_name = jni_generator.ProxyHelpers.GetClass(is_natives_class, 329 module_name) 330 package = jni_generator.ProxyHelpers.GetPackage(is_natives_class, 331 options.package_prefix) 332 333 if forwarding or not (options.use_proxy_hash 334 or options.enable_jni_multiplexing): 335 fields = string.Template("""\ 336 public static final boolean TESTING_ENABLED = ${TESTING_ENABLED}; 337 public static final boolean REQUIRE_MOCK = ${REQUIRE_MOCK}; 338""").substitute({ 339 'TESTING_ENABLED': str(options.enable_proxy_mocks).lower(), 340 'REQUIRE_MOCK': str(options.require_mocks).lower(), 341 }) 342 else: 343 fields = '' 344 345 if forwarding: 346 methods = registration_dict['FORWARDING_PROXY_METHODS'] 347 else: 348 methods = registration_dict['PROXY_NATIVE_SIGNATURES'] 349 350 return template.substitute({ 351 'CLASS_NAME': class_name, 352 'FIELDS': fields, 353 'PACKAGE': package.replace('/', '.'), 354 'METHODS': methods 355 }) 356 357 358def CreateFromDict(options, module_name, registration_dict): 359 """Returns the content of the header file.""" 360 361 template = string.Template("""\ 362// Copyright 2017 The Chromium Authors 363// Use of this source code is governed by a BSD-style license that can be 364// found in the LICENSE file. 365 366 367// This file is autogenerated by 368// base/android/jni_generator/jni_registration_generator.py 369// Please do not change its content. 370 371#ifndef ${HEADER_GUARD} 372#define ${HEADER_GUARD} 373 374#include <jni.h> 375 376#include <iterator> 377 378#include "base/android/jni_generator/jni_generator_helper.h" 379#include "base/android/jni_int_wrapper.h" 380 381 382// Step 1: Forward declarations (classes). 383${CLASS_PATH_DECLARATIONS} 384 385// Step 2: Forward declarations (methods). 386 387${FORWARD_DECLARATIONS} 388${FORWARDING_CALLS} 389${MANUAL_REGISTRATION} 390#endif // ${HEADER_GUARD} 391""") 392 _SetProxyRegistrationFields(options, module_name, registration_dict) 393 if not options.enable_jni_multiplexing: 394 registration_dict['FORWARDING_CALLS'] = '' 395 if len(registration_dict['FORWARD_DECLARATIONS']) == 0: 396 return '' 397 398 return template.substitute(registration_dict) 399 400 401def _GetJavaToNativeParamsList(params_list): 402 if not params_list: 403 return 'jlong switch_num' 404 405 # Parameters are named after their type, with a unique number per parameter 406 # type to make sure the names are unique, even within the same types. 407 params_type_count = collections.defaultdict(int) 408 params_in_stub = [] 409 for p in params_list: 410 params_type_count[p] += 1 411 params_in_stub.append( 412 '%s %s_param%d' % 413 (jni_generator.JavaDataTypeToC(p), p.replace( 414 '[]', '_array').lower(), params_type_count[p])) 415 416 return 'jlong switch_num, ' + ', '.join(params_in_stub) 417 418 419class DictionaryGenerator(object): 420 """Generates an inline header file for JNI registration.""" 421 422 def __init__(self, options, module_name, content_namespace, 423 fully_qualified_class, natives, jni_params): 424 self.options = options 425 self.module_name = module_name 426 self.content_namespace = content_namespace 427 self.natives = natives 428 self.proxy_natives = [n for n in natives if n.is_proxy] 429 self.non_proxy_natives = [n for n in natives if not n.is_proxy] 430 self.fully_qualified_class = fully_qualified_class 431 self.jni_params = jni_params 432 self.class_name = self.fully_qualified_class.split('/')[-1] 433 self.helper = jni_generator.HeaderFileGeneratorHelper( 434 self.class_name, 435 self.module_name, 436 fully_qualified_class, 437 options.use_proxy_hash, 438 options.package_prefix, 439 enable_jni_multiplexing=options.enable_jni_multiplexing) 440 self.registration_dict = None 441 442 def Generate(self): 443 self.registration_dict = { 444 'FULL_CLASS_NAME': self.fully_qualified_class, 445 'MODULE_NAME': self.module_name 446 } 447 self._AddClassPathDeclarations() 448 self._AddForwardDeclaration() 449 self._AddJNINativeMethodsArrays() 450 self._AddProxyNativeMethodKStrings() 451 self._AddRegisterNativesCalls() 452 self._AddRegisterNativesFunctions() 453 454 self.registration_dict['PROXY_NATIVE_SIGNATURES'] = (''.join( 455 _MakeProxySignature(self.options, native) 456 for native in self.proxy_natives)) 457 458 if self.options.enable_jni_multiplexing: 459 self._AssignSwitchNumberToNatives() 460 self._AddCases() 461 462 if self.options.use_proxy_hash or self.options.enable_jni_multiplexing: 463 self.registration_dict['FORWARDING_PROXY_METHODS'] = ('\n'.join( 464 _MakeForwardingProxy(self.options, self.module_name, native) 465 for native in self.proxy_natives)) 466 467 return self.registration_dict 468 469 def _SetDictValue(self, key, value): 470 self.registration_dict[key] = jni_generator.WrapOutput(value) 471 472 def _AddClassPathDeclarations(self): 473 classes = self.helper.GetUniqueClasses(self.natives) 474 self._SetDictValue( 475 'CLASS_PATH_DECLARATIONS', 476 self.helper.GetClassPathLines(classes, declare_only=True)) 477 478 def _AddForwardDeclaration(self): 479 """Add the content of the forward declaration to the dictionary.""" 480 template = string.Template("""\ 481JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}( 482 JNIEnv* env, 483 ${PARAMS_IN_STUB}); 484""") 485 forward_declaration = '' 486 for native in self.natives: 487 value = { 488 'RETURN': jni_generator.JavaDataTypeToC(native.return_type), 489 'STUB_NAME': self.helper.GetStubName(native), 490 'PARAMS_IN_STUB': jni_generator.GetParamsInStub(native), 491 } 492 forward_declaration += template.substitute(value) 493 self._SetDictValue('FORWARD_DECLARATIONS', forward_declaration) 494 495 def _AddRegisterNativesCalls(self): 496 """Add the body of the RegisterNativesImpl method to the dictionary.""" 497 498 # Only register if there is at least 1 non-proxy native 499 if len(self.non_proxy_natives) == 0: 500 return '' 501 502 template = string.Template("""\ 503 if (!${REGISTER_NAME}(env)) 504 return false; 505""") 506 value = { 507 'REGISTER_NAME': 508 jni_generator.GetRegistrationFunctionName(self.fully_qualified_class) 509 } 510 register_body = template.substitute(value) 511 self._SetDictValue('REGISTER_NON_NATIVES', register_body) 512 513 def _AddJNINativeMethodsArrays(self): 514 """Returns the implementation of the array of native methods.""" 515 template = string.Template("""\ 516static const JNINativeMethod kMethods_${JAVA_CLASS}[] = { 517${KMETHODS} 518}; 519 520""") 521 open_namespace = '' 522 close_namespace = '' 523 if self.content_namespace: 524 parts = self.content_namespace.split('::') 525 all_namespaces = ['namespace %s {' % ns for ns in parts] 526 open_namespace = '\n'.join(all_namespaces) + '\n' 527 all_namespaces = ['} // namespace %s' % ns for ns in parts] 528 all_namespaces.reverse() 529 close_namespace = '\n'.join(all_namespaces) + '\n\n' 530 531 body = self._SubstituteNativeMethods(template) 532 if body: 533 self._SetDictValue('JNI_NATIVE_METHOD_ARRAY', ''.join( 534 (open_namespace, body, close_namespace))) 535 536 def _GetKMethodsString(self, clazz): 537 ret = [] 538 for native in self.non_proxy_natives: 539 if (native.java_class_name == clazz 540 or (not native.java_class_name and clazz == self.class_name)): 541 ret += [self._GetKMethodArrayEntry(native)] 542 return '\n'.join(ret) 543 544 def _GetKMethodArrayEntry(self, native): 545 template = string.Template(' { "${NAME}", ${JNI_SIGNATURE}, ' + 546 'reinterpret_cast<void*>(${STUB_NAME}) },') 547 548 name = 'native' + native.name 549 jni_signature = self.jni_params.Signature(native.params, native.return_type) 550 stub_name = self.helper.GetStubName(native) 551 552 if native.is_proxy: 553 # Literal name of the native method in the class that contains the actual 554 # native declaration. 555 if self.options.enable_jni_multiplexing: 556 return_type, params_list = native.return_and_signature 557 class_name = jni_generator.EscapeClassName( 558 jni_generator.ProxyHelpers.GetQualifiedClass( 559 True, self.module_name, self.options.package_prefix)) 560 proxy_signature = jni_generator.EscapeClassName( 561 _GetMultiplexProxyName(return_type, params_list)) 562 563 name = _GetMultiplexProxyName(return_type, params_list) 564 jni_signature = self.jni_params.Signature( 565 [jni_generator.Param(datatype='long', name='switch_num')] + 566 native.params, native.return_type) 567 stub_name = 'Java_' + class_name + '_' + proxy_signature 568 elif self.options.use_proxy_hash: 569 name = native.hashed_proxy_name 570 else: 571 name = native.proxy_name 572 values = { 573 'NAME': name, 574 'JNI_SIGNATURE': jni_signature, 575 'STUB_NAME': stub_name 576 } 577 return template.substitute(values) 578 579 def _AddProxyNativeMethodKStrings(self): 580 """Returns KMethodString for wrapped native methods in all_classes """ 581 582 proxy_k_strings = ('\n'.join( 583 self._GetKMethodArrayEntry(p) for p in self.proxy_natives)) 584 585 self._SetDictValue('PROXY_NATIVE_METHOD_ARRAY', proxy_k_strings) 586 587 def _SubstituteNativeMethods(self, template): 588 """Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided 589 template.""" 590 ret = [] 591 all_classes = self.helper.GetUniqueClasses(self.natives) 592 all_classes[self.class_name] = self.fully_qualified_class 593 594 for clazz, full_clazz in all_classes.items(): 595 if clazz == jni_generator.ProxyHelpers.GetClass( 596 self.options.use_proxy_hash or self.options.enable_jni_multiplexing, 597 self.module_name): 598 continue 599 600 kmethods = self._GetKMethodsString(clazz) 601 namespace_str = '' 602 if self.content_namespace: 603 namespace_str = self.content_namespace + '::' 604 if kmethods: 605 values = { 606 'NAMESPACE': namespace_str, 607 'JAVA_CLASS': jni_generator.EscapeClassName(full_clazz), 608 'KMETHODS': kmethods 609 } 610 ret += [template.substitute(values)] 611 if not ret: return '' 612 return '\n'.join(ret) 613 614 def GetJNINativeMethodsString(self): 615 """Returns the implementation of the array of native methods.""" 616 template = string.Template("""\ 617static const JNINativeMethod kMethods_${JAVA_CLASS}[] = { 618${KMETHODS} 619 620}; 621""") 622 return self._SubstituteNativeMethods(template) 623 624 def _AddRegisterNativesFunctions(self): 625 """Returns the code for RegisterNatives.""" 626 natives = self._GetRegisterNativesImplString() 627 if not natives: 628 return '' 629 template = string.Template("""\ 630JNI_REGISTRATION_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) { 631${NATIVES}\ 632 return true; 633} 634 635""") 636 values = { 637 'REGISTER_NAME': 638 jni_generator.GetRegistrationFunctionName(self.fully_qualified_class), 639 'NATIVES': 640 natives 641 } 642 self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values)) 643 644 def _GetRegisterNativesImplString(self): 645 """Returns the shared implementation for RegisterNatives.""" 646 template = string.Template("""\ 647 const int kMethods_${JAVA_CLASS}Size = 648 std::size(${NAMESPACE}kMethods_${JAVA_CLASS}); 649 if (env->RegisterNatives( 650 ${JAVA_CLASS}_clazz(env), 651 ${NAMESPACE}kMethods_${JAVA_CLASS}, 652 kMethods_${JAVA_CLASS}Size) < 0) { 653 jni_generator::HandleRegistrationError(env, 654 ${JAVA_CLASS}_clazz(env), 655 __FILE__); 656 return false; 657 } 658 659""") 660 # Only register if there is a native method not in a proxy, 661 # since all the proxies will be registered together. 662 if len(self.non_proxy_natives) != 0: 663 return self._SubstituteNativeMethods(template) 664 return '' 665 666 def _AssignSwitchNumberToNatives(self): 667 # The switch number for a native method is a 64-bit long with the first 668 # bit being a sign digit. The signed two's complement is taken when 669 # appropriate to make use of negative numbers. 670 for native in self.proxy_natives: 671 hashed_long = hashlib.md5( 672 native.proxy_name.encode('utf-8')).hexdigest()[:16] 673 switch_num = int(hashed_long, 16) 674 if (switch_num & 1 << 63): 675 switch_num -= (1 << 64) 676 677 native.switch_num = str(switch_num) 678 679 def _AddCases(self): 680 # Switch cases are grouped together by the same proxy signatures. 681 template = string.Template(""" 682 case ${SWITCH_NUM}: 683 return ${STUB_NAME}(env, jcaller${PARAMS}); 684 """) 685 686 signature_to_cases = collections.defaultdict(list) 687 for native in self.proxy_natives: 688 signature = native.return_and_signature 689 params = _GetParamsListForMultiplex(signature[1], with_types=False) 690 values = { 691 'SWITCH_NUM': native.switch_num, 692 # We are forced to call the generated stub instead of the impl because 693 # the impl is not guaranteed to have a globally unique name. 694 'STUB_NAME': self.helper.GetStubName(native), 695 'PARAMS': params, 696 } 697 signature_to_cases[signature].append(template.substitute(values)) 698 699 self.registration_dict['SIGNATURE_TO_CASES'] = signature_to_cases 700 701 702def _GetParamsListForMultiplex(params_list, with_types): 703 if not params_list: 704 return '' 705 706 # Parameters are named after their type, with a unique number per parameter 707 # type to make sure the names are unique, even within the same types. 708 params_type_count = collections.defaultdict(int) 709 params = [] 710 for p in params_list: 711 params_type_count[p] += 1 712 param_type = p + ' ' if with_types else '' 713 params.append( 714 '%s%s_param%d' % 715 (param_type, p.replace('[]', '_array').lower(), params_type_count[p])) 716 717 return ', ' + ', '.join(params) 718 719 720def _GetMultiplexProxyName(return_type, params_list): 721 # Proxy signatures for methods are named after their return type and 722 # parameters to ensure uniqueness, even for the same return types. 723 params = '' 724 if params_list: 725 type_convert_dictionary = { 726 '[]': 'A', 727 'byte': 'B', 728 'char': 'C', 729 'double': 'D', 730 'float': 'F', 731 'int': 'I', 732 'long': 'J', 733 'Class': 'L', 734 'Object': 'O', 735 'String': 'R', 736 'short': 'S', 737 'Throwable': 'T', 738 'boolean': 'Z', 739 } 740 # Parameter types could contain multi-dimensional arrays and every 741 # instance of [] has to be replaced in the proxy signature name. 742 for k, v in type_convert_dictionary.items(): 743 params_list = [p.replace(k, v) for p in params_list] 744 params = '_' + ''.join(p for p in params_list) 745 746 return 'resolve_for_' + return_type.replace('[]', '_array').lower() + params 747 748 749def _MakeForwardingProxy(options, module_name, proxy_native): 750 template = string.Template(""" 751 public static ${RETURN_TYPE} ${METHOD_NAME}(${PARAMS_WITH_TYPES}) { 752 ${MAYBE_RETURN}${PROXY_CLASS}.${PROXY_METHOD_NAME}(${PARAM_NAMES}); 753 }""") 754 755 params_with_types = ', '.join( 756 '%s %s' % (p.datatype, p.name) for p in proxy_native.params) 757 param_names = ', '.join(p.name for p in proxy_native.params) 758 proxy_class = jni_generator.ProxyHelpers.GetQualifiedClass( 759 True, module_name, options.package_prefix) 760 761 if options.enable_jni_multiplexing: 762 if not param_names: 763 param_names = proxy_native.switch_num + 'L' 764 else: 765 param_names = proxy_native.switch_num + 'L, ' + param_names 766 return_type, params_list = proxy_native.return_and_signature 767 proxy_method_name = _GetMultiplexProxyName(return_type, params_list) 768 else: 769 proxy_method_name = proxy_native.hashed_proxy_name 770 771 return template.substitute({ 772 'RETURN_TYPE': 773 proxy_native.return_type, 774 'METHOD_NAME': 775 proxy_native.proxy_name, 776 'PARAMS_WITH_TYPES': 777 params_with_types, 778 'MAYBE_RETURN': 779 '' if proxy_native.return_type == 'void' else 'return ', 780 'PROXY_CLASS': 781 proxy_class.replace('/', '.'), 782 'PROXY_METHOD_NAME': 783 proxy_method_name, 784 'PARAM_NAMES': 785 param_names, 786 }) 787 788 789def _MakeProxySignature(options, proxy_native): 790 params_with_types = ', '.join('%s %s' % (p.datatype, p.name) 791 for p in proxy_native.params) 792 native_method_line = """ 793 public static native ${RETURN} ${PROXY_NAME}(${PARAMS_WITH_TYPES});""" 794 795 if options.enable_jni_multiplexing: 796 # This has to be only one line and without comments because all the proxy 797 # signatures will be joined, then split on new lines with duplicates removed 798 # since multiple |proxy_native|s map to the same multiplexed signature. 799 signature_template = string.Template(native_method_line) 800 801 alt_name = None 802 return_type, params_list = proxy_native.return_and_signature 803 proxy_name = _GetMultiplexProxyName(return_type, params_list) 804 params_with_types = 'long switch_num' + _GetParamsListForMultiplex( 805 params_list, with_types=True) 806 elif options.use_proxy_hash: 807 signature_template = string.Template(""" 808 // Original name: ${ALT_NAME}""" + native_method_line) 809 810 alt_name = proxy_native.proxy_name 811 proxy_name = proxy_native.hashed_proxy_name 812 else: 813 signature_template = string.Template(""" 814 // Hashed name: ${ALT_NAME}""" + native_method_line) 815 816 # We add the prefix that is sometimes used so that codesearch can find it if 817 # someone searches a full method name from the stacktrace. 818 alt_name = f'Java_J_N_{proxy_native.hashed_proxy_name}' 819 proxy_name = proxy_native.proxy_name 820 821 return signature_template.substitute({ 822 'ALT_NAME': alt_name, 823 'RETURN': proxy_native.return_type, 824 'PROXY_NAME': proxy_name, 825 'PARAMS_WITH_TYPES': params_with_types, 826 }) 827 828 829def main(argv): 830 arg_parser = argparse.ArgumentParser() 831 action_helpers.add_depfile_arg(arg_parser) 832 833 arg_parser.add_argument( 834 '--sources-files', 835 required=True, 836 action='append', 837 help='A list of .sources files which contain Java ' 838 'file paths.') 839 arg_parser.add_argument( 840 '--header-path', help='Path to output header file (optional).') 841 arg_parser.add_argument( 842 '--srcjar-path', 843 required=True, 844 help='Path to output srcjar for GEN_JNI.java (and J/N.java if proxy' 845 ' hash is enabled).') 846 arg_parser.add_argument('--file-exclusions', 847 default=[], 848 help='A list of Java files which should be ignored ' 849 'by the parser.') 850 arg_parser.add_argument( 851 '--namespace', 852 default='', 853 help='Native namespace to wrap the registration functions ' 854 'into.') 855 # TODO(crbug.com/898261) hook these flags up to the build config to enable 856 # mocking in instrumentation tests 857 arg_parser.add_argument( 858 '--enable-proxy-mocks', 859 default=False, 860 action='store_true', 861 help='Allows proxy native impls to be mocked through Java.') 862 arg_parser.add_argument( 863 '--require-mocks', 864 default=False, 865 action='store_true', 866 help='Requires all used native implementations to have a mock set when ' 867 'called. Otherwise an exception will be thrown.') 868 arg_parser.add_argument( 869 '--use-proxy-hash', 870 action='store_true', 871 help='Enables hashing of the native declaration for methods in ' 872 'an @JniNatives interface') 873 arg_parser.add_argument( 874 '--module-name', 875 default='', 876 help='Only look at natives annotated with a specific module name.') 877 arg_parser.add_argument( 878 '--enable-jni-multiplexing', 879 action='store_true', 880 help='Enables JNI multiplexing for Java native methods') 881 arg_parser.add_argument( 882 '--manual-jni-registration', 883 action='store_true', 884 help='Manually do JNI registration - required for crazy linker') 885 arg_parser.add_argument('--include-test-only', 886 action='store_true', 887 help='Whether to maintain ForTesting JNI methods.') 888 arg_parser.add_argument( 889 '--package_prefix', 890 help= 891 'Adds a prefix to the classes fully qualified-name. Effectively changing a class name from' 892 'foo.bar -> prefix.foo.bar') 893 args = arg_parser.parse_args(build_utils.ExpandFileArgs(argv[1:])) 894 895 if not args.enable_proxy_mocks and args.require_mocks: 896 arg_parser.error( 897 'Invalid arguments: --require-mocks without --enable-proxy-mocks. ' 898 'Cannot require mocks if they are not enabled.') 899 if not args.header_path and args.manual_jni_registration: 900 arg_parser.error( 901 'Invalid arguments: --manual-jni-registration without --header-path. ' 902 'Cannot manually register JNI if there is no output header file.') 903 904 sources_files = sorted(set(action_helpers.parse_gn_list(args.sources_files))) 905 906 java_file_paths = [] 907 for f in sources_files: 908 # Skip generated files, since the GN targets do not declare any deps. Also 909 # skip Kotlin files as they are not supported by JNI generation. 910 java_file_paths.extend( 911 p for p in build_utils.ReadSourcesList(f) if p.startswith('..') 912 and p not in args.file_exclusions and not p.endswith('.kt')) 913 _Generate(args, java_file_paths) 914 915 if args.depfile: 916 action_helpers.write_depfile(args.depfile, args.srcjar_path, 917 sources_files + java_file_paths) 918 919 920if __name__ == '__main__': 921 sys.exit(main(sys.argv)) 922