1# Copyright 2012 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 "intermediates" command.""" 5 6import base64 7import collections 8import dataclasses 9import hashlib 10import os 11import re 12import shutil 13from string import Template 14import subprocess 15import sys 16import tempfile 17import textwrap 18import zipfile 19 20_FILE_DIR = os.path.dirname(__file__) 21_CHROMIUM_SRC = os.path.join(_FILE_DIR, os.pardir, os.pardir) 22_BUILD_ANDROID_GYP = os.path.join(_CHROMIUM_SRC, 'build', 'android', 'gyp') 23 24# Item 0 of sys.path is the directory of the main file; item 1 is PYTHONPATH 25# (if set); item 2 is system libraries. 26sys.path.insert(1, _BUILD_ANDROID_GYP) 27 28from codegen import placeholder_gen_jni_java 29from codegen import proxy_impl_java 30import common 31import java_types 32import parse 33import proxy 34 35# Use 100 columns rather than 80 because it makes many lines more readable. 36_WRAP_LINE_LENGTH = 100 37# WrapOutput() is fairly slow. Pre-creating TextWrappers helps a bit. 38_WRAPPERS_BY_INDENT = [ 39 textwrap.TextWrapper(width=_WRAP_LINE_LENGTH, 40 expand_tabs=False, 41 replace_whitespace=False, 42 subsequent_indent=' ' * (indent + 4), 43 break_long_words=False) for indent in range(50) 44] # 50 chosen experimentally. 45 46 47class NativeMethod: 48 """Describes a C/C++ method that is called by Java.""" 49 def __init__(self, parsed_method, *, java_class, is_proxy): 50 self.name = parsed_method.name 51 self.signature = parsed_method.signature 52 self.is_proxy = is_proxy 53 self.static = is_proxy or parsed_method.static 54 self.native_class_name = parsed_method.native_class_name 55 56 # Proxy methods don't have a native prefix so the first letter is 57 # lowercase. But we still want the CPP declaration to use upper camel 58 # case for the method name. 59 self.cpp_name = common.capitalize(self.name) 60 self.is_test_only = _NameIsTestOnly(self.name) 61 62 if self.is_proxy: 63 self.proxy_signature = self.signature.to_proxy() 64 self.proxy_name, self.hashed_proxy_name = proxy.create_method_names( 65 java_class, self.name, self.is_test_only) 66 self.switch_num = None 67 else: 68 self.proxy_signature = self.signature 69 70 first_param = self.params and self.params[0] 71 if (first_param and first_param.java_type.is_primitive() 72 and first_param.java_type.primitive_name == 'long' 73 and first_param.name.startswith('native')): 74 if parsed_method.native_class_name: 75 self.first_param_cpp_type = parsed_method.native_class_name 76 else: 77 self.first_param_cpp_type = first_param.name[len('native'):] 78 else: 79 self.first_param_cpp_type = None 80 81 @property 82 def return_type(self): 83 return self.signature.return_type 84 85 @property 86 def proxy_return_type(self): 87 return self.proxy_signature.return_type 88 89 @property 90 def params(self): 91 return self.signature.param_list 92 93 @property 94 def proxy_params(self): 95 return self.proxy_signature.param_list 96 97 98class CalledByNative: 99 """Describes a java method exported to c/c++""" 100 def __init__(self, 101 parsed_called_by_native, 102 *, 103 is_system_class, 104 unchecked=False): 105 self.name = parsed_called_by_native.name 106 self.signature = parsed_called_by_native.signature 107 self.static = parsed_called_by_native.static 108 self.unchecked = parsed_called_by_native.unchecked or unchecked 109 self.java_class = parsed_called_by_native.java_class 110 self.is_system_class = is_system_class 111 112 # Computed once we know if overloads exist. 113 self.method_id_function_name = None 114 115 @property 116 def is_constructor(self): 117 return self.name == '<init>' 118 119 @property 120 def return_type(self): 121 return self.signature.return_type 122 123 @property 124 def params(self): 125 return self.signature.param_list 126 127 @property 128 def method_id_var_name(self): 129 return f'{self.method_id_function_name}{len(self.params)}' 130 131 132def JavaTypeToCForDeclaration(java_type): 133 """Wrap the C datatype in a JavaParamRef if required.""" 134 c_type = java_type.to_cpp() 135 if java_type.is_primitive(): 136 return c_type 137 return f'const base::android::JavaParamRef<{c_type}>&' 138 139 140def JavaTypeToCForCalledByNativeParam(java_type): 141 """Returns a C datatype to be when calling from native.""" 142 c_type = java_type.to_cpp() 143 if java_type.is_primitive(): 144 if c_type == 'jint': 145 return 'JniIntWrapper' 146 return c_type 147 return f'const base::android::JavaRef<{c_type}>&' 148 149 150def _GetJNIFirstParam(native, for_declaration): 151 c_type = 'jclass' if native.static else 'jobject' 152 153 if for_declaration: 154 c_type = f'const base::android::JavaParamRef<{c_type}>&' 155 return [c_type + ' jcaller'] 156 157 158def _GetParamsInDeclaration(native): 159 """Returns the params for the forward declaration. 160 161 Args: 162 native: the native dictionary describing the method. 163 164 Returns: 165 A string containing the params. 166 """ 167 ret = [ 168 JavaTypeToCForDeclaration(p.java_type) + ' ' + p.name 169 for p in native.params 170 ] 171 if not native.static: 172 ret = _GetJNIFirstParam(native, True) + ret 173 return ret 174 175 176def GetParamsInStub(native): 177 """Returns the params for the stub declaration. 178 179 Args: 180 native: the native dictionary describing the method. 181 182 Returns: 183 A string containing the params. 184 """ 185 params = [p.java_type.to_cpp() + ' ' + p.name for p in native.params] 186 params = _GetJNIFirstParam(native, False) + params 187 return ',\n '.join(params) 188 189 190def _NameIsTestOnly(name): 191 return name.endswith(('ForTest', 'ForTests', 'ForTesting')) 192 193 194def GetRegistrationFunctionName(fully_qualified_class): 195 """Returns the register name with a given class.""" 196 return 'RegisterNative_' + common.escape_class_name(fully_qualified_class) 197 198 199def _StaticCastForType(java_type): 200 if java_type.is_primitive(): 201 return None 202 ret = java_type.to_cpp() 203 return None if ret == 'jobject' else ret 204 205 206def _GetEnvCall(called_by_native): 207 """Maps the types available via env->Call__Method.""" 208 if called_by_native.is_constructor: 209 return 'NewObject' 210 if called_by_native.return_type.is_primitive(): 211 name = called_by_native.return_type.primitive_name 212 call = common.capitalize(called_by_native.return_type.primitive_name) 213 else: 214 call = 'Object' 215 if called_by_native.static: 216 call = 'Static' + call 217 return 'Call' + call + 'Method' 218 219 220def _MangleMethodName(type_resolver, name, param_types): 221 mangled_types = [] 222 for java_type in param_types: 223 if java_type.primitive_name: 224 part = java_type.primitive_name 225 else: 226 part = type_resolver.contextualize(java_type.java_class).replace('.', '_') 227 mangled_types.append(part + ('Array' * java_type.array_dimensions)) 228 229 return f'{name}__' + '__'.join(mangled_types) 230 231 232def _AssignMethodIdFunctionNames(type_resolver, called_by_natives): 233 # Mangle names for overloads with different number of parameters. 234 def key(called_by_native): 235 return (called_by_native.java_class.full_name_with_slashes, 236 called_by_native.name, len(called_by_native.params)) 237 238 method_counts = collections.Counter(key(x) for x in called_by_natives) 239 240 for called_by_native in called_by_natives: 241 if called_by_native.is_constructor: 242 method_id_function_name = 'Constructor' 243 else: 244 method_id_function_name = called_by_native.name 245 246 if method_counts[key(called_by_native)] > 1: 247 method_id_function_name = _MangleMethodName( 248 type_resolver, method_id_function_name, 249 called_by_native.signature.param_types) 250 251 called_by_native.method_id_function_name = method_id_function_name 252 253 254# Removes empty lines that are indented (i.e. start with 2x spaces). 255def RemoveIndentedEmptyLines(string): 256 return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE) 257 258 259class JNIFromJavaP: 260 """Uses 'javap' to parse a .class file and generate the JNI header file.""" 261 def __init__(self, parsed_file, options): 262 self.options = options 263 self.type_resolver = parsed_file.type_resolver 264 265 called_by_natives = [] 266 for parsed_called_by_native in parsed_file.called_by_natives: 267 called_by_natives.append( 268 CalledByNative(parsed_called_by_native, 269 unchecked=options.unchecked_exceptions, 270 is_system_class=True)) 271 _AssignMethodIdFunctionNames(parsed_file.type_resolver, called_by_natives) 272 self.called_by_natives = called_by_natives 273 274 self.constant_fields = parsed_file.constant_fields 275 self.jni_namespace = options.namespace or 'JNI_' + self.java_class.name 276 277 def GetContent(self): 278 # We pass in an empty string for the module (which will make the JNI use the 279 # base module's files) for all javap-derived JNI. There may be a way to get 280 # the module from a jar file, but it's not needed right now. 281 generator = InlHeaderFileGenerator('', self.jni_namespace, self.java_class, 282 [], self.called_by_natives, 283 self.constant_fields, self.type_resolver, 284 self.options) 285 return generator.GetContent() 286 287 @property 288 def java_class(self): 289 return self.type_resolver.java_class 290 291 292class JNIFromJavaSource: 293 """Uses the given java source file to generate the JNI header file.""" 294 def __init__(self, parsed_file, options): 295 self.options = options 296 self.filename = parsed_file.filename 297 self.type_resolver = parsed_file.type_resolver 298 self.jni_namespace = parsed_file.jni_namespace or options.namespace 299 self.module_name = parsed_file.module_name 300 self.proxy_interface = parsed_file.proxy_interface 301 self.proxy_visibility = parsed_file.proxy_visibility 302 303 natives = [] 304 for parsed_method in parsed_file.proxy_methods: 305 natives.append( 306 NativeMethod(parsed_method, java_class=self.java_class, 307 is_proxy=True)) 308 309 for parsed_method in parsed_file.non_proxy_methods: 310 natives.append( 311 NativeMethod(parsed_method, 312 java_class=self.java_class, 313 is_proxy=False)) 314 315 self.natives = natives 316 317 called_by_natives = [] 318 for parsed_called_by_native in parsed_file.called_by_natives: 319 called_by_natives.append( 320 CalledByNative(parsed_called_by_native, is_system_class=False)) 321 322 _AssignMethodIdFunctionNames(parsed_file.type_resolver, called_by_natives) 323 self.called_by_natives = called_by_natives 324 325 @property 326 def java_class(self): 327 return self.type_resolver.java_class 328 329 @property 330 def proxy_natives(self): 331 return [n for n in self.natives if n.is_proxy] 332 333 @property 334 def non_proxy_natives(self): 335 return [n for n in self.natives if not n.is_proxy] 336 337 def RemoveTestOnlyNatives(self): 338 self.natives = [n for n in self.natives if not n.is_test_only] 339 340 def GetContent(self): 341 generator = InlHeaderFileGenerator(self.module_name, self.jni_namespace, 342 self.java_class, self.natives, 343 self.called_by_natives, [], 344 self.type_resolver, self.options) 345 return generator.GetContent() 346 347 348class HeaderFileGeneratorHelper(object): 349 """Include helper methods for header generators.""" 350 def __init__(self, 351 java_class, 352 *, 353 module_name, 354 package_prefix=None, 355 split_name=None, 356 use_proxy_hash=False, 357 enable_jni_multiplexing=False): 358 self.class_name = java_class.name 359 self.module_name = module_name 360 self.fully_qualified_class = java_class.full_name_with_slashes 361 self.use_proxy_hash = use_proxy_hash 362 self.package_prefix = package_prefix 363 self.split_name = split_name 364 self.enable_jni_multiplexing = enable_jni_multiplexing 365 self.gen_jni_class = proxy.get_gen_jni_class(short=use_proxy_hash 366 or enable_jni_multiplexing, 367 name_prefix=module_name, 368 package_prefix=package_prefix) 369 370 def GetStubName(self, native): 371 """Return the name of the stub function for this native method. 372 373 Args: 374 native: the native dictionary describing the method. 375 376 Returns: 377 A string with the stub function name (used by the JVM). 378 """ 379 if native.is_proxy: 380 if self.use_proxy_hash: 381 method_name = common.escape_class_name(native.hashed_proxy_name) 382 else: 383 method_name = common.escape_class_name(native.proxy_name) 384 return 'Java_%s_%s' % (common.escape_class_name( 385 self.gen_jni_class.full_name_with_slashes), method_name) 386 387 template = Template('Java_${JAVA_NAME}_native${NAME}') 388 389 java_name = self.fully_qualified_class 390 391 values = { 392 'NAME': native.cpp_name, 393 'JAVA_NAME': common.escape_class_name(java_name) 394 } 395 return template.substitute(values) 396 397 def GetUniqueClasses(self, origin): 398 ret = collections.OrderedDict() 399 for entry in origin: 400 if isinstance(entry, NativeMethod) and entry.is_proxy: 401 short_name = self.use_proxy_hash or self.enable_jni_multiplexing 402 ret[self.gen_jni_class.name] = self.gen_jni_class.full_name_with_slashes 403 continue 404 ret[self.class_name] = self.fully_qualified_class 405 406 if isinstance(entry, CalledByNative): 407 class_name = entry.java_class.name 408 jni_class_path = entry.java_class.full_name_with_slashes 409 else: 410 class_name = self.class_name 411 jni_class_path = self.fully_qualified_class 412 ret[class_name] = jni_class_path 413 return ret 414 415 def GetClassPathLines(self, classes, declare_only=False): 416 """Returns the ClassPath constants.""" 417 ret = [] 418 if declare_only: 419 template = Template(""" 420extern const char kClassPath_${JAVA_CLASS}[]; 421""") 422 else: 423 template = Template(""" 424JNI_ZERO_COMPONENT_BUILD_EXPORT extern const char kClassPath_${JAVA_CLASS}[]; 425const char kClassPath_${JAVA_CLASS}[] = \ 426"${JNI_CLASS_PATH}"; 427""") 428 429 for full_clazz in classes.values(): 430 values = { 431 'JAVA_CLASS': common.escape_class_name(full_clazz), 432 'JNI_CLASS_PATH': full_clazz, 433 } 434 # Since all proxy methods use the same class, defining this in every 435 # header file would result in duplicated extern initializations. 436 if full_clazz != self.gen_jni_class.full_name_with_slashes: 437 ret += [template.substitute(values)] 438 439 class_getter = """\ 440#ifndef ${JAVA_CLASS}_clazz_defined 441#define ${JAVA_CLASS}_clazz_defined 442inline jclass ${JAVA_CLASS}_clazz(JNIEnv* env) { 443 return base::android::LazyGetClass(env, kClassPath_${JAVA_CLASS}, \ 444${MAYBE_SPLIT_NAME_ARG}&g_${JAVA_CLASS}_clazz); 445} 446#endif 447""" 448 if declare_only: 449 template = Template("""\ 450extern std::atomic<jclass> g_${JAVA_CLASS}_clazz; 451""" + class_getter) 452 else: 453 template = Template("""\ 454// Leaking this jclass as we cannot use LazyInstance from some threads. 455JNI_ZERO_COMPONENT_BUILD_EXPORT std::atomic<jclass> g_${JAVA_CLASS}_clazz(nullptr); 456""" + class_getter) 457 458 for full_clazz in classes.values(): 459 values = { 460 'JAVA_CLASS': 461 common.escape_class_name(full_clazz), 462 'MAYBE_SPLIT_NAME_ARG': 463 (('"%s", ' % self.split_name) if self.split_name else '') 464 } 465 # Since all proxy methods use the same class, defining this in every 466 # header file would result in duplicated extern initializations. 467 if full_clazz != self.gen_jni_class.full_name_with_slashes: 468 ret += [template.substitute(values)] 469 470 return ''.join(ret) 471 472 473class InlHeaderFileGenerator(object): 474 """Generates an inline header file for JNI integration.""" 475 def __init__(self, module_name, namespace, java_class, natives, 476 called_by_natives, constant_fields, type_resolver, options): 477 self.namespace = namespace 478 self.java_class = java_class 479 self.class_name = java_class.name 480 self.natives = natives 481 self.called_by_natives = called_by_natives 482 self.header_guard = java_class.full_name_with_slashes.replace('/', 483 '_') + '_JNI' 484 self.constant_fields = constant_fields 485 self.type_resolver = type_resolver 486 self.options = options 487 488 # from-jar does not define these flags. 489 kwargs = {} 490 if hasattr(options, 'use_proxy_hash'): 491 kwargs['use_proxy_hash'] = options.use_proxy_hash 492 kwargs['enable_jni_multiplexing'] = options.enable_jni_multiplexing 493 kwargs['package_prefix'] = options.package_prefix 494 495 self.helper = HeaderFileGeneratorHelper(java_class, 496 module_name=module_name, 497 split_name=options.split_name, 498 **kwargs) 499 500 def GetContent(self): 501 """Returns the content of the JNI binding file.""" 502 template = Template("""\ 503// Copyright 2014 The Chromium Authors 504// Use of this source code is governed by a BSD-style license that can be 505// found in the LICENSE file. 506 507 508// This file is autogenerated by 509// ${SCRIPT_NAME} 510// For 511// ${FULLY_QUALIFIED_CLASS} 512 513#ifndef ${HEADER_GUARD} 514#define ${HEADER_GUARD} 515 516#include <jni.h> 517 518#include "third_party/jni_zero/jni_export.h" 519${INCLUDES} 520 521// Step 1: Forward declarations. 522$CLASS_PATH_DEFINITIONS 523 524// Step 2: Constants (optional). 525 526$CONSTANT_FIELDS\ 527 528// Step 3: Method stubs. 529$METHOD_STUBS 530 531#endif // ${HEADER_GUARD} 532""") 533 values = { 534 'SCRIPT_NAME': GetScriptName(), 535 'FULLY_QUALIFIED_CLASS': self.java_class.full_name_with_slashes, 536 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), 537 'CONSTANT_FIELDS': self.GetConstantFieldsString(), 538 'METHOD_STUBS': self.GetMethodStubsString(), 539 'HEADER_GUARD': self.header_guard, 540 'INCLUDES': self.GetIncludesString(), 541 } 542 open_namespace = self.GetOpenNamespaceString() 543 if open_namespace: 544 close_namespace = self.GetCloseNamespaceString() 545 values['METHOD_STUBS'] = '\n'.join( 546 [open_namespace, values['METHOD_STUBS'], close_namespace]) 547 548 constant_fields = values['CONSTANT_FIELDS'] 549 if constant_fields: 550 values['CONSTANT_FIELDS'] = '\n'.join( 551 [open_namespace, constant_fields, close_namespace]) 552 553 return WrapOutput(template.substitute(values)) 554 555 def GetClassPathDefinitionsString(self): 556 classes = self.helper.GetUniqueClasses(self.called_by_natives) 557 classes.update(self.helper.GetUniqueClasses(self.natives)) 558 return self.helper.GetClassPathLines(classes) 559 560 def GetConstantFieldsString(self): 561 if not self.constant_fields: 562 return '' 563 ret = ['enum Java_%s_constant_fields {' % self.java_class.name] 564 for c in self.constant_fields: 565 ret += [' %s = %s,' % (c.name, c.value)] 566 ret += ['};', ''] 567 return '\n'.join(ret) 568 569 def GetMethodStubsString(self): 570 """Returns the code corresponding to method stubs.""" 571 ret = [] 572 for native in self.natives: 573 ret += [self.GetNativeStub(native)] 574 ret += self.GetLazyCalledByNativeMethodStubs() 575 return '\n'.join(ret) 576 577 def GetLazyCalledByNativeMethodStubs(self): 578 return [ 579 self.GetLazyCalledByNativeMethodStub(called_by_native) 580 for called_by_native in self.called_by_natives 581 ] 582 583 def GetIncludesString(self): 584 if not self.options.extra_includes: 585 return '' 586 includes = self.options.extra_includes 587 return '\n'.join('#include "%s"' % x for x in includes) + '\n' 588 589 def GetOpenNamespaceString(self): 590 if self.namespace: 591 all_namespaces = [ 592 'namespace %s {' % ns for ns in self.namespace.split('::') 593 ] 594 return '\n'.join(all_namespaces) + '\n' 595 return '' 596 597 def GetCloseNamespaceString(self): 598 if self.namespace: 599 all_namespaces = [ 600 '} // namespace %s' % ns for ns in self.namespace.split('::') 601 ] 602 all_namespaces.reverse() 603 return '\n' + '\n'.join(all_namespaces) 604 return '' 605 606 def GetCalledByNativeParamsInDeclaration(self, called_by_native): 607 return ',\n '.join([ 608 JavaTypeToCForCalledByNativeParam(p.java_type) + ' ' + p.name 609 for p in called_by_native.params 610 ]) 611 612 def GetJavaParamRefForCall(self, c_type, name): 613 return Template( 614 'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({ 615 'TYPE': 616 c_type, 617 'NAME': 618 name, 619 }) 620 621 def GetImplementationMethodName(self, native): 622 return 'JNI_%s_%s' % (self.java_class.name, native.cpp_name) 623 624 def GetNativeStub(self, native): 625 if native.first_param_cpp_type: 626 params = native.params[1:] 627 else: 628 params = native.params 629 630 params_in_call = ['env'] 631 if not native.static: 632 # Add jcaller param. 633 params_in_call.append(self.GetJavaParamRefForCall('jobject', 'jcaller')) 634 635 for p in params: 636 if p.java_type.is_primitive(): 637 params_in_call.append(p.name) 638 else: 639 c_type = p.java_type.to_cpp() 640 params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name)) 641 642 params_in_declaration = _GetParamsInDeclaration(native) 643 params_in_call = ', '.join(params_in_call) 644 645 return_type = native.return_type.to_cpp() 646 return_declaration = return_type 647 post_call = '' 648 if not native.return_type.is_primitive(): 649 post_call = '.Release()' 650 return_declaration = ('base::android::ScopedJavaLocalRef<' + return_type + 651 '>') 652 653 values = { 654 'RETURN': return_type, 655 'RETURN_DECLARATION': return_declaration, 656 'NAME': native.cpp_name, 657 'IMPL_METHOD_NAME': self.GetImplementationMethodName(native), 658 'PARAMS': ',\n '.join(params_in_declaration), 659 'PARAMS_IN_STUB': GetParamsInStub(native), 660 'PARAMS_IN_CALL': params_in_call, 661 'POST_CALL': post_call, 662 'STUB_NAME': self.helper.GetStubName(native), 663 } 664 665 namespace_qual = self.namespace + '::' if self.namespace else '' 666 if native.first_param_cpp_type: 667 optional_error_return = native.return_type.to_cpp_default_value() 668 if optional_error_return: 669 optional_error_return = ', ' + optional_error_return 670 values.update({ 671 'OPTIONAL_ERROR_RETURN': optional_error_return, 672 'PARAM0_NAME': native.params[0].name, 673 'P0_TYPE': native.first_param_cpp_type, 674 }) 675 template = Template("""\ 676JNI_BOUNDARY_EXPORT ${RETURN} ${STUB_NAME}( 677 JNIEnv* env, 678 ${PARAMS_IN_STUB}) { 679 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); 680 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN}); 681 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL}; 682} 683""") 684 else: 685 if values['PARAMS']: 686 values['PARAMS'] = ', ' + values['PARAMS'] 687 template = Template("""\ 688static ${RETURN_DECLARATION} ${IMPL_METHOD_NAME}(JNIEnv* env${PARAMS}); 689 690JNI_BOUNDARY_EXPORT ${RETURN} ${STUB_NAME}( 691 JNIEnv* env, 692 ${PARAMS_IN_STUB}) { 693 return ${IMPL_METHOD_NAME}(${PARAMS_IN_CALL})${POST_CALL}; 694} 695""") 696 697 return RemoveIndentedEmptyLines(template.substitute(values)) 698 699 def GetArgument(self, param): 700 if param.java_type.is_primitive(): 701 if param.java_type.primitive_name == 'int': 702 return f'as_jint({param.name})' 703 return param.name 704 return f'{param.name}.obj()' 705 706 def GetCalledByNativeValues(self, called_by_native): 707 """Fills in necessary values for the CalledByNative methods.""" 708 java_class_only = called_by_native.java_class.nested_name 709 java_class = called_by_native.java_class.full_name_with_slashes 710 711 if called_by_native.static or called_by_native.is_constructor: 712 first_param_in_declaration = '' 713 first_param_in_call = 'clazz' 714 else: 715 first_param_in_declaration = ( 716 ', const base::android::JavaRef<jobject>& obj') 717 first_param_in_call = 'obj.obj()' 718 params_in_declaration = self.GetCalledByNativeParamsInDeclaration( 719 called_by_native) 720 if params_in_declaration: 721 params_in_declaration = ', ' + params_in_declaration 722 params_in_call = ', '.join( 723 self.GetArgument(p) for p in called_by_native.params) 724 if params_in_call: 725 params_in_call = ', ' + params_in_call 726 check_exception = 'Unchecked' 727 method_id_member_name = 'call_context.method_id' 728 if not called_by_native.unchecked: 729 check_exception = 'Checked' 730 method_id_member_name = 'call_context.base.method_id' 731 if called_by_native.is_constructor: 732 return_type = called_by_native.java_class.as_type() 733 else: 734 return_type = called_by_native.return_type 735 pre_call = '' 736 post_call = '' 737 static_cast = _StaticCastForType(return_type) 738 if static_cast: 739 pre_call = f'static_cast<{static_cast}>(' 740 post_call = ')' 741 optional_error_return = return_type.to_cpp_default_value() 742 if optional_error_return: 743 optional_error_return = ', ' + optional_error_return 744 return_declaration = '' 745 return_clause = '' 746 return_type_str = return_type.to_cpp() 747 if not return_type.is_void(): 748 pre_call = ' ' + pre_call 749 return_declaration = return_type_str + ' ret =' 750 if return_type.is_primitive(): 751 return_clause = 'return ret;' 752 else: 753 return_type_str = ( 754 f'base::android::ScopedJavaLocalRef<{return_type_str}>') 755 return_clause = f'return {return_type_str}(env, ret);' 756 sig = called_by_native.signature 757 jni_descriptor = sig.to_descriptor() 758 759 return { 760 'JAVA_CLASS_ONLY': java_class_only, 761 'JAVA_CLASS': common.escape_class_name(java_class), 762 'RETURN_TYPE': return_type_str, 763 'OPTIONAL_ERROR_RETURN': optional_error_return, 764 'RETURN_DECLARATION': return_declaration, 765 'RETURN_CLAUSE': return_clause, 766 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, 767 'PARAMS_IN_DECLARATION': params_in_declaration, 768 'PRE_CALL': pre_call, 769 'POST_CALL': post_call, 770 'ENV_CALL': _GetEnvCall(called_by_native), 771 'FIRST_PARAM_IN_CALL': first_param_in_call, 772 'PARAMS_IN_CALL': params_in_call, 773 'CHECK_EXCEPTION': check_exception, 774 'JNI_NAME': called_by_native.name, 775 'JNI_DESCRIPTOR': jni_descriptor, 776 'METHOD_ID_MEMBER_NAME': method_id_member_name, 777 'METHOD_ID_FUNCTION_NAME': called_by_native.method_id_function_name, 778 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, 779 'METHOD_ID_TYPE': 'STATIC' if called_by_native.static else 'INSTANCE', 780 } 781 782 def GetLazyCalledByNativeMethodStub(self, called_by_native): 783 """Returns a string.""" 784 function_signature_template = Template("""\ 785static ${RETURN_TYPE} Java_${JAVA_CLASS_ONLY}_${METHOD_ID_FUNCTION_NAME}(\ 786JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""") 787 function_header_template = Template("""\ 788${FUNCTION_SIGNATURE} {""") 789 function_header_with_unused_template = Template("""\ 790[[maybe_unused]] ${FUNCTION_SIGNATURE}; 791${FUNCTION_SIGNATURE} {""") 792 template = Template(""" 793static std::atomic<jmethodID> g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(nullptr); 794${FUNCTION_HEADER} 795 jclass clazz = ${JAVA_CLASS}_clazz(env); 796 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL}, 797 ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN}); 798 799 jni_generator::JniJavaCallContext${CHECK_EXCEPTION} call_context; 800 call_context.Init< 801 base::android::MethodID::TYPE_${METHOD_ID_TYPE}>( 802 env, 803 clazz, 804 "${JNI_NAME}", 805 "${JNI_DESCRIPTOR}", 806 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); 807 808 ${RETURN_DECLARATION} 809 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, 810 ${METHOD_ID_MEMBER_NAME}${PARAMS_IN_CALL})${POST_CALL}; 811 ${RETURN_CLAUSE} 812}""") 813 values = self.GetCalledByNativeValues(called_by_native) 814 values['FUNCTION_SIGNATURE'] = ( 815 function_signature_template.substitute(values)) 816 if called_by_native.is_system_class: 817 values['FUNCTION_HEADER'] = ( 818 function_header_with_unused_template.substitute(values)) 819 else: 820 values['FUNCTION_HEADER'] = function_header_template.substitute(values) 821 return RemoveIndentedEmptyLines(template.substitute(values)) 822 823 def GetTraceEventForNameTemplate(self, name_template, values): 824 name = Template(name_template).substitute(values) 825 return ' TRACE_EVENT0("jni", "%s");\n' % name 826 827 828def WrapOutput(output): 829 ret = [] 830 for line in output.splitlines(): 831 # Do not wrap preprocessor directives or comments. 832 if len(line) < _WRAP_LINE_LENGTH or line[0] == '#' or line.startswith('//'): 833 ret.append(line) 834 else: 835 # Assumes that the line is not already indented as a continuation line, 836 # which is not always true (oh well). 837 first_line_indent = (len(line) - len(line.lstrip())) 838 wrapper = _WRAPPERS_BY_INDENT[first_line_indent] 839 ret.extend(wrapper.wrap(line)) 840 ret += [''] 841 return '\n'.join(ret) 842 843 844def GetScriptName(): 845 script_components = os.path.abspath(__file__).split(os.path.sep) 846 base_index = 0 847 for idx, value in enumerate(script_components): 848 if value == 'base' or value == 'third_party': 849 base_index = idx 850 break 851 return os.sep.join(script_components[base_index:]) 852 853 854def _RemoveStaleHeaders(path, output_names): 855 if not os.path.isdir(path): 856 return 857 # Do not remove output files so that timestamps on declared outputs are not 858 # modified unless their contents are changed (avoids reverse deps needing to 859 # be rebuilt). 860 preserve = set(output_names) 861 for root, _, files in os.walk(path): 862 for f in files: 863 if f not in preserve: 864 file_path = os.path.join(root, f) 865 if os.path.isfile(file_path) and file_path.endswith('.h'): 866 os.remove(file_path) 867 868 869def _CheckSameModule(jni_objs): 870 files_by_module = collections.defaultdict(list) 871 for jni_obj in jni_objs: 872 if jni_obj.proxy_natives: 873 files_by_module[jni_obj.module_name].append(jni_obj.filename) 874 if len(files_by_module) > 1: 875 sys.stderr.write( 876 'Multiple values for @NativeMethods(moduleName) is not supported.\n') 877 for module_name, filenames in files_by_module.items(): 878 sys.stderr.write(f'module_name={module_name}\n') 879 for filename in filenames: 880 sys.stderr.write(f' {filename}\n') 881 sys.exit(1) 882 883 884def _CheckNotEmpty(jni_objs): 885 has_empty = False 886 for jni_obj in jni_objs: 887 if not (jni_obj.natives or jni_obj.called_by_natives): 888 has_empty = True 889 sys.stderr.write(f'No native methods found in {jni_obj.filename}.\n') 890 if has_empty: 891 sys.exit(1) 892 893 894def _RunJavap(javap_path, class_file): 895 p = subprocess.run([javap_path, '-s', '-constants', class_file], 896 text=True, 897 capture_output=True, 898 check=True) 899 return p.stdout 900 901 902def _ParseClassFiles(jar_file, class_files, args): 903 # Parse javap output. 904 ret = [] 905 with tempfile.TemporaryDirectory() as temp_dir: 906 with zipfile.ZipFile(jar_file) as z: 907 z.extractall(temp_dir, class_files) 908 for class_file in class_files: 909 class_file = os.path.join(temp_dir, class_file) 910 contents = _RunJavap(args.javap, class_file) 911 parsed_file = parse.parse_javap(class_file, contents) 912 ret.append(JNIFromJavaP(parsed_file, args)) 913 return ret 914 915 916def _CreateSrcJar(srcjar_path, gen_jni_class, jni_objs, *, script_name): 917 with common.atomic_output(srcjar_path) as f: 918 with zipfile.ZipFile(f, 'w') as srcjar: 919 for jni_obj in jni_objs: 920 if not jni_obj.proxy_natives: 921 continue 922 content = proxy_impl_java.Generate(jni_obj, 923 gen_jni_class=gen_jni_class, 924 script_name=script_name) 925 zip_path = f'{jni_obj.java_class.class_without_prefix.full_name_with_slashes}Jni.java' 926 common.add_to_zip_hermetic(srcjar, zip_path, data=content) 927 928 content = placeholder_gen_jni_java.Generate(jni_objs, 929 gen_jni_class=gen_jni_class, 930 script_name=script_name) 931 zip_path = f'{gen_jni_class.full_name_with_slashes}.java' 932 common.add_to_zip_hermetic(srcjar, zip_path, data=content) 933 934 935def _WriteHeaders(jni_objs, output_names, output_dir): 936 for jni_obj, header_name in zip(jni_objs, output_names): 937 output_file = os.path.join(output_dir, header_name) 938 content = jni_obj.GetContent() 939 with common.atomic_output(output_file, 'w') as f: 940 f.write(content) 941 942 943def _ParseSourceFiles(args): 944 jni_objs = [] 945 for f in args.input_files: 946 parsed_file = parse.parse_java_file(f, package_prefix=args.package_prefix) 947 jni_objs.append(JNIFromJavaSource(parsed_file, args)) 948 return jni_objs 949 950 951def GenerateFromSource(parser, args): 952 # Remove existing headers so that moving .java source files but not updating 953 # the corresponding C++ include will be a compile failure (otherwise 954 # incremental builds will usually not catch this). 955 _RemoveStaleHeaders(args.output_dir, args.output_names) 956 957 try: 958 jni_objs = _ParseSourceFiles(args) 959 _CheckNotEmpty(jni_objs) 960 _CheckSameModule(jni_objs) 961 except parse.ParseError as e: 962 sys.stderr.write(f'{e}\n') 963 sys.exit(1) 964 965 _WriteHeaders(jni_objs, args.output_names, args.output_dir) 966 967 # Write .srcjar 968 if args.srcjar_path: 969 # module_name is set only for proxy_natives. 970 jni_objs = [x for x in jni_objs if x.proxy_natives] 971 if jni_objs: 972 gen_jni_class = proxy.get_gen_jni_class( 973 short=False, 974 name_prefix=jni_objs[0].module_name, 975 package_prefix=args.package_prefix) 976 _CreateSrcJar(args.srcjar_path, 977 gen_jni_class, 978 jni_objs, 979 script_name=GetScriptName()) 980 else: 981 # Only @CalledByNatives. 982 zipfile.ZipFile(args.srcjar_path, 'w').close() 983 984 985def GenerateFromJar(parser, args): 986 if not args.javap: 987 args.javap = shutil.which('javap') 988 if not args.javap: 989 parser.error('Could not find "javap" on your PATH. Use --javap to ' 990 'specify its location.') 991 992 # Remove existing headers so that moving .java source files but not updating 993 # the corresponding C++ include will be a compile failure (otherwise 994 # incremental builds will usually not catch this). 995 _RemoveStaleHeaders(args.output_dir, args.output_names) 996 997 try: 998 jni_objs = _ParseClassFiles(args.jar_file, args.input_files, args) 999 except parse.ParseError as e: 1000 sys.stderr.write(f'{e}\n') 1001 sys.exit(1) 1002 1003 _WriteHeaders(jni_objs, args.output_names, args.output_dir) 1004