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 itertools 9import multiprocessing 10import os 11import pathlib 12import pickle 13import posixpath 14import re 15import string 16import sys 17import zipfile 18 19from codegen import header_common 20from codegen import natives_header 21import common 22import java_types 23import jni_generator 24import parse 25import proxy 26 27 28_SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN = "<INSERT HERE>" 29 30# All but FULL_CLASS_NAME, which is used only for sorting. 31MERGEABLE_KEYS = [ 32 'CLASS_ACCESSORS', 33 'FORWARD_DECLARATIONS', 34 'JNI_NATIVE_METHOD', 35 'JNI_NATIVE_METHOD_ARRAY', 36 'PROXY_NATIVE_SIGNATURES', 37 'FORWARDING_PROXY_METHODS', 38 'PROXY_NATIVE_METHOD_ARRAY', 39 'REGISTER_NATIVES', 40] 41 42 43def _ParseHelper(package_prefix, path): 44 return parse.parse_java_file(path, package_prefix=package_prefix) 45 46 47def _LoadJniObjs(paths, options): 48 ret = {} 49 if all(p.endswith('.jni.pickle') for p in paths): 50 for pickle_path in paths: 51 with open(pickle_path, 'rb') as f: 52 parsed_files = pickle.load(f) 53 ret[pickle_path] = [ 54 jni_generator.JniObject(pf, options, from_javap=False) 55 for pf in parsed_files 56 ] 57 else: 58 func = functools.partial(_ParseHelper, options.package_prefix) 59 with multiprocessing.Pool() as pool: 60 for pf in pool.imap_unordered(func, paths): 61 ret[pf.filename] = [ 62 jni_generator.JniObject(pf, options, from_javap=False) 63 ] 64 65 return ret 66 67 68def _FilterJniObjs(jni_objs_by_path, options): 69 for jni_objs in jni_objs_by_path.values(): 70 # Remove test-only methods. 71 if not options.include_test_only: 72 for jni_obj in jni_objs: 73 jni_obj.RemoveTestOnlyNatives() 74 # Ignoring non-active modules and empty natives lists. 75 jni_objs[:] = [ 76 o for o in jni_objs 77 if o.natives and o.module_name == options.module_name 78 ] 79 80 81def _Flatten(jni_objs_by_path, paths): 82 return itertools.chain(*(jni_objs_by_path[p] for p in paths)) 83 84 85def _Generate(options, native_sources, java_sources): 86 """Generates files required to perform JNI registration. 87 88 Generates a srcjar containing a single class, GEN_JNI, that contains all 89 native method declarations. 90 91 Optionally generates a header file that provides RegisterNatives to perform 92 JNI registration. 93 94 Args: 95 options: arguments from the command line 96 native_sources: A list of .jni.pickle or .java file paths taken from native 97 dependency tree. The source of truth. 98 java_sources: A list of .jni.pickle or .java file paths. Used to assert 99 against native_sources. 100 """ 101 # The native-based sources are the "source of truth" - the Java based ones 102 # will be used later to generate stubs and make assertions. 103 jni_objs_by_path = _LoadJniObjs(set(native_sources + java_sources), options) 104 _FilterJniObjs(jni_objs_by_path, options) 105 106 dicts = [] 107 for jni_obj in _Flatten(jni_objs_by_path, native_sources): 108 dicts.append(DictionaryGenerator(jni_obj, options).Generate()) 109 # Sort to make output deterministic. 110 dicts.sort(key=lambda d: d['FULL_CLASS_NAME']) 111 112 stubs = _GenerateStubsAndAssert(options, jni_objs_by_path, native_sources, 113 java_sources) 114 combined_dict = {} 115 for key in MERGEABLE_KEYS: 116 combined_dict[key] = ''.join(d.get(key, '') for d in dicts) 117 118 short_gen_jni_class = proxy.get_gen_jni_class( 119 short=True, 120 name_prefix=options.module_name, 121 package_prefix=options.package_prefix) 122 full_gen_jni_class = proxy.get_gen_jni_class( 123 short=False, 124 name_prefix=options.module_name, 125 package_prefix=options.package_prefix) 126 if options.use_proxy_hash or options.enable_jni_multiplexing: 127 gen_jni_class = short_gen_jni_class 128 else: 129 gen_jni_class = full_gen_jni_class 130 # PROXY_NATIVE_SIGNATURES and PROXY_NATIVE_METHOD_ARRAY will have 131 # duplicates for JNI multiplexing since all native methods with similar 132 # signatures map to the same proxy. Similarly, there may be multiple switch 133 # case entries for the same proxy signatures. 134 if options.enable_jni_multiplexing: 135 proxy_signatures_list = sorted( 136 set(combined_dict['PROXY_NATIVE_SIGNATURES'].split('\n'))) 137 combined_dict['PROXY_NATIVE_SIGNATURES'] = '\n'.join( 138 signature for signature in proxy_signatures_list) 139 140 proxy_native_array_list = sorted( 141 set(combined_dict['PROXY_NATIVE_METHOD_ARRAY'].split('},\n'))) 142 combined_dict['PROXY_NATIVE_METHOD_ARRAY'] = '},\n'.join( 143 p for p in proxy_native_array_list if p != '') + '}' 144 signature_to_cases = collections.defaultdict(list) 145 for d in dicts: 146 for signature, cases in d['SIGNATURE_TO_CASES'].items(): 147 signature_to_cases[signature].extend(cases) 148 combined_dict[ 149 'FORWARDING_PROXY_METHODS'] = _InsertMultiplexingSwitchNumbers( 150 signature_to_cases, combined_dict['FORWARDING_PROXY_METHODS'], 151 short_gen_jni_class) 152 combined_dict['FORWARDING_CALLS'] = _AddForwardingCalls( 153 signature_to_cases, short_gen_jni_class) 154 155 if options.header_path: 156 combined_dict['NAMESPACE'] = options.namespace or '' 157 header_content = CreateFromDict(gen_jni_class, options, combined_dict) 158 with common.atomic_output(options.header_path, mode='w') as f: 159 f.write(header_content) 160 161 stub_methods_string = ''.join(stubs) 162 163 with common.atomic_output(options.srcjar_path) as f: 164 with zipfile.ZipFile(f, 'w') as srcjar: 165 if options.use_proxy_hash or options.enable_jni_multiplexing: 166 # J/N.java 167 common.add_to_zip_hermetic( 168 srcjar, 169 f'{short_gen_jni_class.full_name_with_slashes}.java', 170 data=CreateProxyJavaFromDict(options, gen_jni_class, combined_dict)) 171 # org/jni_zero/GEN_JNI.java 172 common.add_to_zip_hermetic( 173 srcjar, 174 f'{full_gen_jni_class.full_name_with_slashes}.java', 175 data=CreateProxyJavaFromDict(options, 176 full_gen_jni_class, 177 combined_dict, 178 stub_methods=stub_methods_string, 179 forwarding=True)) 180 else: 181 # org/jni_zero/GEN_JNI.java 182 common.add_to_zip_hermetic( 183 srcjar, 184 f'{full_gen_jni_class.full_name_with_slashes}.java', 185 data=CreateProxyJavaFromDict(options, 186 gen_jni_class, 187 combined_dict, 188 stub_methods=stub_methods_string)) 189 190 191def _GenerateStubsAndAssert(options, jni_objs_by_path, native_sources, 192 java_sources): 193 native_sources_set = set(native_sources) 194 java_sources_set = set(java_sources) 195 native_only = native_sources_set - java_sources_set 196 java_only = java_sources_set - native_sources_set 197 198 java_only_jni_objs = sorted(_Flatten(jni_objs_by_path, java_only), 199 key=lambda jni_obj: jni_obj.filename) 200 native_only_jni_objs = sorted(_Flatten(jni_objs_by_path, native_only), 201 key=lambda jni_obj: jni_obj.filename) 202 failed = False 203 if not options.add_stubs_for_missing_native and java_only_jni_objs: 204 failed = True 205 warning_message = '''Failed JNI assertion! 206We reference Java files which use JNI, but our native library does not depend on 207the corresponding generate_jni(). 208To bypass this check, add stubs to Java with --add-stubs-for-missing-jni. 209Excess Java files: 210''' 211 sys.stderr.write(warning_message) 212 sys.stderr.write('\n'.join(jni_obj.filename 213 for jni_obj in java_only_jni_objs)) 214 sys.stderr.write('\n') 215 if not options.remove_uncalled_methods and native_only_jni_objs: 216 failed = True 217 warning_message = '''Failed JNI assertion! 218Our native library depends on generate_jnis which reference Java files that we 219do not include in our final dex. 220To bypass this check, delete these extra methods with --remove-uncalled-jni. 221Unneeded Java files: 222''' 223 sys.stderr.write(warning_message) 224 sys.stderr.write('\n'.join(native_only_jni_objs.filename 225 for jni_obj in native_only_jni_objs)) 226 sys.stderr.write('\n') 227 if failed: 228 sys.exit(1) 229 230 return [ 231 _GenerateStubs(jni_obj.proxy_natives) for jni_obj in java_only_jni_objs 232 ] 233 234 235def _GenerateStubs(natives): 236 final_string = '' 237 for native in natives: 238 template = string.Template(""" 239 240 public static ${RETURN_TYPE} ${METHOD_NAME}(${PARAMS_WITH_TYPES}) { 241 throw new RuntimeException("Stub - not implemented!"); 242 }""") 243 244 final_string += template.substitute({ 245 'RETURN_TYPE': 246 native.proxy_return_type.to_java(), 247 'METHOD_NAME': 248 native.proxy_name, 249 'PARAMS_WITH_TYPES': 250 native.proxy_params.to_java_declaration(), 251 }) 252 return final_string 253 254 255def _InsertMultiplexingSwitchNumbers(signature_to_cases, java_functions_string, 256 short_gen_jni_class): 257 switch_case_method_name_re = re.compile('return (\w+)\(') 258 java_function_call_re = re.compile('public static \S+ (\w+)\(') 259 method_to_switch_num = {} 260 for signature, cases in sorted(signature_to_cases.items()): 261 for i, case in enumerate(cases): 262 assert _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN in case 263 method_name = switch_case_method_name_re.search(case).group(1) 264 method_to_switch_num[method_name] = i 265 cases[i] = case.replace(_SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN, str(i)) 266 267 swaps = {} 268 for match in java_function_call_re.finditer(java_functions_string): 269 unhashed_java_name = match.group(1) 270 is_test_only = jni_generator.NameIsTestOnly(unhashed_java_name) 271 hashed = proxy.create_hashed_method_name(unhashed_java_name, is_test_only) 272 fully_qualified_hash = f'{short_gen_jni_class.full_name_with_slashes}/{hashed}' 273 cpp_hash_name = 'Java_' + common.escape_class_name(fully_qualified_hash) 274 switch_num = method_to_switch_num[cpp_hash_name] 275 replace_location = java_functions_string.find( 276 _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN, match.end()) 277 swaps[replace_location] = switch_num 278 279 # Doing a seperate pass to construct the new string for efficiency - don't 280 # want to do thousands of copies of a massive string. 281 new_java_functions_string = "" 282 prev_loc = 0 283 for loc, num in sorted(swaps.items()): 284 new_java_functions_string += java_functions_string[prev_loc:loc] 285 new_java_functions_string += str(num) 286 prev_loc = loc + len(_SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN) 287 new_java_functions_string += java_functions_string[prev_loc:] 288 return new_java_functions_string 289 290 291 292 293def _AddForwardingCalls(signature_to_cases, short_gen_jni_class): 294 template = string.Template(""" 295JNI_BOUNDARY_EXPORT ${RETURN} Java_${CLASS_NAME}_${PROXY_SIGNATURE}( 296 JNIEnv* env, 297 jclass jcaller, 298 ${PARAMS_IN_STUB}) { 299 switch (switch_num) { 300 ${CASES} 301 default: 302 JNI_ZERO_ELOG("${CLASS_NAME}_${PROXY_SIGNATURE} was called with an \ 303invalid switch number: %d", switch_num); 304 JNI_ZERO_DCHECK(false); 305 return${DEFAULT_RETURN}; 306 } 307}""") 308 309 switch_statements = [] 310 for signature, cases in sorted(signature_to_cases.items()): 311 params_in_stub = _GetJavaToNativeParamsList(signature.param_types) 312 switch_statements.append( 313 template.substitute({ 314 'RETURN': 315 signature.return_type.to_cpp(), 316 'CLASS_NAME': 317 common.escape_class_name( 318 short_gen_jni_class.full_name_with_slashes), 319 'PROXY_SIGNATURE': 320 common.escape_class_name(_GetMultiplexProxyName(signature)), 321 'PARAMS_IN_STUB': 322 params_in_stub, 323 'CASES': 324 ''.join(cases), 325 'DEFAULT_RETURN': 326 '' if signature.return_type.is_void() else ' {}', 327 })) 328 329 return ''.join(s for s in switch_statements) 330 331 332def _SetProxyRegistrationFields(options, gen_jni_class, registration_dict): 333 registration_template = string.Template("""\ 334 335static const JNINativeMethod kMethods_${ESCAPED_PROXY_CLASS}[] = { 336${KMETHODS} 337}; 338 339namespace { 340 341JNI_ZERO_COMPONENT_BUILD_EXPORT bool ${REGISTRATION_NAME}(JNIEnv* env) { 342 const int number_of_methods = std::size(kMethods_${ESCAPED_PROXY_CLASS}); 343 344 jni_zero::ScopedJavaLocalRef<jclass> native_clazz = 345 jni_zero::GetClass(env, "${PROXY_CLASS}"); 346 if (env->RegisterNatives( 347 native_clazz.obj(), 348 kMethods_${ESCAPED_PROXY_CLASS}, 349 number_of_methods) < 0) { 350 351 jni_zero::internal::HandleRegistrationError(env, native_clazz.obj(), __FILE__); 352 return false; 353 } 354 355 return true; 356} 357 358} // namespace 359""") 360 361 registration_call = string.Template("""\ 362 363 // Register natives in a proxy. 364 if (!${REGISTRATION_NAME}(env)) { 365 return false; 366 } 367""") 368 369 manual_registration = string.Template("""\ 370// Method declarations. 371 372${JNI_NATIVE_METHOD_ARRAY}\ 373${PROXY_NATIVE_METHOD_ARRAY}\ 374 375${JNI_NATIVE_METHOD} 376// Registration function. 377 378namespace ${NAMESPACE} { 379 380bool RegisterNatives(JNIEnv* env) {\ 381${REGISTER_PROXY_NATIVES} 382${REGISTER_NATIVES} 383 return true; 384} 385 386} // namespace ${NAMESPACE} 387""") 388 389 short_name = options.use_proxy_hash or options.enable_jni_multiplexing 390 sub_dict = { 391 'ESCAPED_PROXY_CLASS': 392 common.escape_class_name(gen_jni_class.full_name_with_slashes), 393 'PROXY_CLASS': 394 gen_jni_class.full_name_with_slashes.replace("/", "."), 395 'KMETHODS': 396 registration_dict['PROXY_NATIVE_METHOD_ARRAY'], 397 'REGISTRATION_NAME': 398 _GetRegistrationFunctionName(gen_jni_class.full_name_with_slashes), 399 } 400 401 if registration_dict['PROXY_NATIVE_METHOD_ARRAY']: 402 proxy_native_array = registration_template.substitute(sub_dict) 403 proxy_natives_registration = registration_call.substitute(sub_dict) 404 else: 405 proxy_native_array = '' 406 proxy_natives_registration = '' 407 408 registration_dict['PROXY_NATIVE_METHOD_ARRAY'] = proxy_native_array 409 registration_dict['REGISTER_PROXY_NATIVES'] = proxy_natives_registration 410 411 if options.manual_jni_registration: 412 registration_dict['MANUAL_REGISTRATION'] = manual_registration.substitute( 413 registration_dict) 414 else: 415 registration_dict['MANUAL_REGISTRATION'] = '' 416 417 418def CreateProxyJavaFromDict(options, 419 gen_jni_class, 420 registration_dict, 421 stub_methods='', 422 forwarding=False): 423 template = string.Template("""\ 424// Copyright 2018 The Chromium Authors 425// Use of this source code is governed by a BSD-style license that can be 426// found in the LICENSE file. 427 428package ${PACKAGE}; 429 430// This file is autogenerated by 431// third_party/jni_zero/jni_registration_generator.py 432// Please do not change its content. 433 434public class ${CLASS_NAME} { 435${FIELDS} 436${METHODS} 437} 438""") 439 440 if forwarding or not (options.use_proxy_hash 441 or options.enable_jni_multiplexing): 442 fields = string.Template("""\ 443 public static final boolean TESTING_ENABLED = ${TESTING_ENABLED}; 444 public static final boolean REQUIRE_MOCK = ${REQUIRE_MOCK}; 445""").substitute({ 446 'TESTING_ENABLED': str(options.enable_proxy_mocks).lower(), 447 'REQUIRE_MOCK': str(options.require_mocks).lower(), 448 }) 449 else: 450 fields = '' 451 452 if forwarding: 453 methods = registration_dict['FORWARDING_PROXY_METHODS'] 454 else: 455 methods = registration_dict['PROXY_NATIVE_SIGNATURES'] 456 methods += stub_methods 457 458 return template.substitute({ 459 'CLASS_NAME': gen_jni_class.name, 460 'FIELDS': fields, 461 'PACKAGE': gen_jni_class.package_with_dots, 462 'METHODS': methods 463 }) 464 465 466def CreateFromDict(gen_jni_class, options, registration_dict): 467 """Returns the content of the header file.""" 468 header_guard = os.path.splitext(options.header_path)[0].upper() + '_' 469 header_guard = re.sub(r'[/.-]', '_', header_guard) 470 471 preamble, epilogue = header_common.header_preamble( 472 jni_generator.GetScriptName(), 473 gen_jni_class, 474 system_includes=['iterator'], # For std::size(). 475 user_includes=['third_party/jni_zero/jni_zero_internal.h'], 476 header_guard=header_guard) 477 registration_dict['PREAMBLE'] = preamble 478 registration_dict['EPILOGUE'] = epilogue 479 template = string.Template("""\ 480${PREAMBLE} 481 482${CLASS_ACCESSORS} 483// Forward declarations (methods). 484 485${FORWARD_DECLARATIONS} 486${FORWARDING_CALLS} 487${MANUAL_REGISTRATION} 488${EPILOGUE} 489""") 490 _SetProxyRegistrationFields(options, gen_jni_class, registration_dict) 491 if not options.enable_jni_multiplexing: 492 registration_dict['FORWARDING_CALLS'] = '' 493 if len(registration_dict['FORWARD_DECLARATIONS']) == 0: 494 return '' 495 496 return template.substitute(registration_dict) 497 498 499def _GetJavaToNativeParamsList(param_types): 500 if not param_types: 501 return 'jint switch_num' 502 503 # Parameters are named after their type, with a unique number per parameter 504 # type to make sure the names are unique, even within the same types. 505 params_type_count = collections.defaultdict(int) 506 params_in_stub = [] 507 for t in param_types: 508 params_type_count[t] += 1 509 params_in_stub.append('%s %s_param%d' % (t.to_cpp(), t.to_java().replace( 510 '[]', '_array').lower(), params_type_count[t])) 511 512 return 'jint switch_num, ' + ', '.join(params_in_stub) 513 514 515def _GetRegistrationFunctionName(fully_qualified_class): 516 """Returns the register name with a given class.""" 517 return 'RegisterNative_' + common.escape_class_name(fully_qualified_class) 518 519 520class DictionaryGenerator(object): 521 """Generates an inline header file for JNI registration.""" 522 def __init__(self, jni_obj, options): 523 self.options = options 524 self.file_path = jni_obj.filename 525 self.content_namespace = jni_obj.jni_namespace 526 self.natives = jni_obj.natives 527 self.proxy_natives = jni_obj.proxy_natives 528 self.non_proxy_natives = jni_obj.non_proxy_natives 529 self.fully_qualified_class = jni_obj.java_class.full_name_with_slashes 530 self.type_resolver = jni_obj.type_resolver 531 self.class_name = jni_obj.java_class.name 532 self.registration_dict = None 533 self.jni_obj = jni_obj 534 self.gen_jni_class = proxy.get_gen_jni_class( 535 short=options.use_proxy_hash or options.enable_jni_multiplexing, 536 name_prefix=options.module_name, 537 package_prefix=options.package_prefix) 538 539 def Generate(self): 540 # GEN_JNI is handled separately. 541 java_classes_with_natives = sorted( 542 set(n.java_class for n in self.jni_obj.non_proxy_natives)) 543 544 self.registration_dict = { 545 'FULL_CLASS_NAME': self.fully_qualified_class, 546 'FILE_PATH': self.file_path, 547 } 548 self.registration_dict['CLASS_ACCESSORS'] = (header_common.class_accessors( 549 java_classes_with_natives, self.jni_obj.module_name)) 550 551 self._AddForwardDeclaration() 552 self._AddJNINativeMethodsArrays(java_classes_with_natives) 553 self._AddProxyNativeMethodKStrings() 554 self._AddRegisterNativesCalls() 555 self._AddRegisterNativesFunctions(java_classes_with_natives) 556 557 self.registration_dict['PROXY_NATIVE_SIGNATURES'] = (''.join( 558 _MakeProxySignature(self.options, native) 559 for native in self.proxy_natives)) 560 561 if self.options.enable_jni_multiplexing: 562 self._AddCases() 563 564 if self.options.use_proxy_hash or self.options.enable_jni_multiplexing: 565 self.registration_dict['FORWARDING_PROXY_METHODS'] = ('\n'.join( 566 _MakeForwardingProxy(self.options, self.gen_jni_class, native) 567 for native in self.proxy_natives)) 568 569 return self.registration_dict 570 571 def _SetDictValue(self, key, value): 572 self.registration_dict[key] = jni_generator.WrapOutput(value) 573 574 def _AddForwardDeclaration(self): 575 """Add the content of the forward declaration to the dictionary.""" 576 sb = common.StringBuilder() 577 for native in self.natives: 578 with sb.statement(): 579 natives_header.proxy_declaration(sb, self.jni_obj, native) 580 self._SetDictValue('FORWARD_DECLARATIONS', sb.to_string()) 581 582 def _AddRegisterNativesCalls(self): 583 """Add the body of the RegisterNativesImpl method to the dictionary.""" 584 585 # Only register if there is at least 1 non-proxy native 586 if len(self.non_proxy_natives) == 0: 587 return '' 588 589 template = string.Template("""\ 590 if (!${REGISTER_NAME}(env)) 591 return false; 592""") 593 value = { 594 'REGISTER_NAME': 595 _GetRegistrationFunctionName(self.fully_qualified_class) 596 } 597 register_body = template.substitute(value) 598 self._SetDictValue('REGISTER_NATIVES', register_body) 599 600 def _AddJNINativeMethodsArrays(self, java_classes_with_natives): 601 """Returns the implementation of the array of native methods.""" 602 template = string.Template("""\ 603static const JNINativeMethod kMethods_${JAVA_CLASS}[] = { 604${KMETHODS} 605}; 606 607""") 608 open_namespace = '' 609 close_namespace = '' 610 if self.content_namespace: 611 parts = self.content_namespace.split('::') 612 all_namespaces = ['namespace %s {' % ns for ns in parts] 613 open_namespace = '\n'.join(all_namespaces) + '\n' 614 all_namespaces = ['} // namespace %s' % ns for ns in parts] 615 all_namespaces.reverse() 616 close_namespace = '\n'.join(all_namespaces) + '\n\n' 617 618 body = self._SubstituteNativeMethods(java_classes_with_natives, template) 619 if body: 620 self._SetDictValue('JNI_NATIVE_METHOD_ARRAY', ''.join( 621 (open_namespace, body, close_namespace))) 622 623 def _GetKMethodsString(self, clazz): 624 if clazz != self.class_name: 625 return '' 626 ret = [self._GetKMethodArrayEntry(n) for n in self.non_proxy_natives] 627 return '\n'.join(ret) 628 629 def _GetKMethodArrayEntry(self, native): 630 template = string.Template(' { "${NAME}", "${JNI_DESCRIPTOR}", ' + 631 'reinterpret_cast<void*>(${STUB_NAME}) },') 632 633 name = 'native' + native.cpp_name 634 jni_descriptor = native.proxy_signature.to_descriptor() 635 stub_name = self.jni_obj.GetStubName(native) 636 637 if native.is_proxy: 638 # Literal name of the native method in the class that contains the actual 639 # native declaration. 640 if self.options.enable_jni_multiplexing: 641 class_name = common.escape_class_name( 642 self.gen_jni_class.full_name_with_slashes) 643 name = _GetMultiplexProxyName(native.proxy_signature) 644 proxy_signature = common.escape_class_name(name) 645 stub_name = 'Java_' + class_name + '_' + proxy_signature 646 647 multipliexed_signature = java_types.JavaSignature( 648 native.return_type, (java_types.LONG, ), None) 649 jni_descriptor = multipliexed_signature.to_descriptor() 650 elif self.options.use_proxy_hash: 651 name = native.hashed_proxy_name 652 else: 653 name = native.proxy_name 654 values = { 655 'NAME': name, 656 'JNI_DESCRIPTOR': jni_descriptor, 657 'STUB_NAME': stub_name 658 } 659 return template.substitute(values) 660 661 def _AddProxyNativeMethodKStrings(self): 662 """Returns KMethodString for wrapped native methods in all_classes """ 663 664 proxy_k_strings = ('\n'.join( 665 self._GetKMethodArrayEntry(p) for p in self.proxy_natives)) 666 667 self._SetDictValue('PROXY_NATIVE_METHOD_ARRAY', proxy_k_strings) 668 669 def _SubstituteNativeMethods(self, java_classes, template): 670 """Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided 671 template.""" 672 ret = [] 673 674 for java_class in java_classes: 675 clazz = java_class.name 676 full_clazz = java_class.full_name_with_slashes 677 678 kmethods = self._GetKMethodsString(clazz) 679 namespace_str = '' 680 if self.content_namespace: 681 namespace_str = self.content_namespace + '::' 682 if kmethods: 683 values = { 684 'NAMESPACE': 685 namespace_str, 686 'JAVA_CLASS': 687 common.escape_class_name(full_clazz), 688 'JAVA_CLASS_ACCESSOR': 689 header_common.class_accessor_expression(java_class), 690 'KMETHODS': 691 kmethods 692 } 693 ret += [template.substitute(values)] 694 return '\n'.join(ret) 695 696 def _AddRegisterNativesFunctions(self, java_classes_with_natives): 697 """Returns the code for RegisterNatives.""" 698 if not java_classes_with_natives: 699 return '' 700 natives = self._GetRegisterNativesImplString(java_classes_with_natives) 701 template = string.Template("""\ 702JNI_ZERO_COMPONENT_BUILD_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) { 703${NATIVES}\ 704 return true; 705} 706 707""") 708 values = { 709 'REGISTER_NAME': 710 _GetRegistrationFunctionName(self.fully_qualified_class), 711 'NATIVES': natives 712 } 713 self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values)) 714 715 def _GetRegisterNativesImplString(self, java_classes_with_natives): 716 """Returns the shared implementation for RegisterNatives.""" 717 template = string.Template("""\ 718 const int kMethods_${JAVA_CLASS}Size = 719 std::size(${NAMESPACE}kMethods_${JAVA_CLASS}); 720 if (env->RegisterNatives( 721 ${JAVA_CLASS_ACCESSOR}, 722 ${NAMESPACE}kMethods_${JAVA_CLASS}, 723 kMethods_${JAVA_CLASS}Size) < 0) { 724 jni_zero::internal::HandleRegistrationError(env, 725 ${JAVA_CLASS_ACCESSOR}, 726 __FILE__); 727 return false; 728 } 729 730""") 731 return self._SubstituteNativeMethods(java_classes_with_natives, template) 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': _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN, 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.jni_obj.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 = _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN 820 else: 821 param_names = _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN + ', ' + 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 = 'int 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