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