1#!/usr/bin/env python 2# Copyright 2017 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Generates GEN_JNI.java (or N.java) and helper for manual JNI registration. 7 8Creates a header file with two static functions: RegisterMainDexNatives() and 9RegisterNonMainDexNatives(). Together, these will use manual JNI registration 10to register all native methods that exist within an application.""" 11 12import argparse 13import functools 14import multiprocessing 15import os 16import string 17import sys 18import zipfile 19 20import jni_generator 21from util import build_utils 22 23# All but FULL_CLASS_NAME, which is used only for sorting. 24MERGEABLE_KEYS = [ 25 'CLASS_PATH_DECLARATIONS', 26 'FORWARD_DECLARATIONS', 27 'JNI_NATIVE_METHOD', 28 'JNI_NATIVE_METHOD_ARRAY', 29 'PROXY_NATIVE_SIGNATURES', 30 'PROXY_NATIVE_METHOD_ARRAY', 31 'PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX', 32 'REGISTER_MAIN_DEX_NATIVES', 33 'REGISTER_NON_MAIN_DEX_NATIVES', 34] 35 36 37def _Generate(java_file_paths, 38 srcjar_path, 39 proxy_opts, 40 header_path=None, 41 namespace=''): 42 """Generates files required to perform JNI registration. 43 44 Generates a srcjar containing a single class, GEN_JNI, that contains all 45 native method declarations. 46 47 Optionally generates a header file that provides functions 48 (RegisterMainDexNatives and RegisterNonMainDexNatives) to perform 49 JNI registration. 50 51 Args: 52 java_file_paths: A list of java file paths. 53 srcjar_path: Path to the GEN_JNI srcjar. 54 header_path: If specified, generates a header file in this location. 55 namespace: If specified, sets the namespace for the generated header file. 56 """ 57 # Without multiprocessing, script takes ~13 seconds for chrome_public_apk 58 # on a z620. With multiprocessing, takes ~2 seconds. 59 pool = multiprocessing.Pool() 60 61 results = [] 62 for d in pool.imap_unordered( 63 functools.partial(_DictForPath, use_proxy_hash=proxy_opts.use_hash), 64 java_file_paths): 65 if d: 66 results.append(d) 67 pool.close() 68 69 # Sort to make output deterministic. 70 results.sort(key=lambda d: d['FULL_CLASS_NAME']) 71 72 combined_dict = {} 73 for key in MERGEABLE_KEYS: 74 combined_dict[key] = ''.join(d.get(key, '') for d in results) 75 76 if header_path: 77 combined_dict['HEADER_GUARD'] = \ 78 os.path.splitext(header_path)[0].replace('/', '_').upper() + '_' 79 combined_dict['NAMESPACE'] = namespace 80 header_content = CreateFromDict(combined_dict, proxy_opts.use_hash) 81 with build_utils.AtomicOutput(header_path, mode='w') as f: 82 f.write(header_content) 83 84 with build_utils.AtomicOutput(srcjar_path) as f: 85 with zipfile.ZipFile(f, 'w') as srcjar: 86 build_utils.AddToZipHermetic( 87 srcjar, 88 '%s.java' % jni_generator.ProxyHelpers.GetQualifiedClass( 89 proxy_opts.use_hash), 90 data=CreateProxyJavaFromDict(combined_dict, proxy_opts)) 91 92 93def _DictForPath(path, use_proxy_hash=False): 94 with open(path) as f: 95 contents = jni_generator.RemoveComments(f.read()) 96 if '@JniIgnoreNatives' in contents: 97 return None 98 99 fully_qualified_class = jni_generator.ExtractFullyQualifiedJavaClassName( 100 path, contents) 101 natives = jni_generator.ExtractNatives(contents, 'long') 102 103 natives += jni_generator.ProxyHelpers.ExtractStaticProxyNatives( 104 fully_qualified_class=fully_qualified_class, 105 contents=contents, 106 ptr_type='long', 107 use_hash=use_proxy_hash) 108 if len(natives) == 0: 109 return None 110 namespace = jni_generator.ExtractJNINamespace(contents) 111 jni_params = jni_generator.JniParams(fully_qualified_class) 112 jni_params.ExtractImportsAndInnerClasses(contents) 113 is_main_dex = jni_generator.IsMainDexJavaClass(contents) 114 header_generator = HeaderGenerator(namespace, fully_qualified_class, natives, 115 jni_params, is_main_dex, use_proxy_hash) 116 return header_generator.Generate() 117 118 119def _SetProxyRegistrationFields(registration_dict, use_hash): 120 registration_template = string.Template("""\ 121 122static const JNINativeMethod kMethods_${ESCAPED_PROXY_CLASS}[] = { 123${KMETHODS} 124}; 125 126namespace { 127 128JNI_REGISTRATION_EXPORT bool ${REGISTRATION_NAME}(JNIEnv* env) { 129 const int number_of_methods = base::size(kMethods_${ESCAPED_PROXY_CLASS}); 130 131 base::android::ScopedJavaLocalRef<jclass> native_clazz = 132 base::android::GetClass(env, "${PROXY_CLASS}"); 133 if (env->RegisterNatives( 134 native_clazz.obj(), 135 kMethods_${ESCAPED_PROXY_CLASS}, 136 number_of_methods) < 0) { 137 138 jni_generator::HandleRegistrationError(env, native_clazz.obj(), __FILE__); 139 return false; 140 } 141 142 return true; 143} 144 145} // namespace 146""") 147 148 registration_call = string.Template("""\ 149 150 // Register natives in a proxy. 151 if (!${REGISTRATION_NAME}(env)) { 152 return false; 153 } 154""") 155 156 sub_dict = { 157 'ESCAPED_PROXY_CLASS': 158 jni_generator.EscapeClassName( 159 jni_generator.ProxyHelpers.GetQualifiedClass(use_hash)), 160 'PROXY_CLASS': 161 jni_generator.ProxyHelpers.GetQualifiedClass(use_hash), 162 'KMETHODS': 163 registration_dict['PROXY_NATIVE_METHOD_ARRAY'], 164 'REGISTRATION_NAME': 165 jni_generator.GetRegistrationFunctionName( 166 jni_generator.ProxyHelpers.GetQualifiedClass(use_hash)), 167 } 168 169 if registration_dict['PROXY_NATIVE_METHOD_ARRAY']: 170 proxy_native_array = registration_template.substitute(sub_dict) 171 proxy_natives_registration = registration_call.substitute(sub_dict) 172 else: 173 proxy_native_array = '' 174 proxy_natives_registration = '' 175 176 if registration_dict['PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX']: 177 sub_dict['REGISTRATION_NAME'] += 'MAIN_DEX' 178 sub_dict['ESCAPED_PROXY_CLASS'] += 'MAIN_DEX' 179 sub_dict['KMETHODS'] = ( 180 registration_dict['PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX']) 181 proxy_native_array += registration_template.substitute(sub_dict) 182 main_dex_call = registration_call.substitute(sub_dict) 183 else: 184 main_dex_call = '' 185 186 registration_dict['PROXY_NATIVE_METHOD_ARRAY'] = proxy_native_array 187 registration_dict['REGISTER_PROXY_NATIVES'] = proxy_natives_registration 188 registration_dict['REGISTER_MAIN_DEX_PROXY_NATIVES'] = main_dex_call 189 190 191def CreateProxyJavaFromDict(registration_dict, proxy_opts): 192 template = string.Template("""\ 193// Copyright 2018 The Chromium Authors. All rights reserved. 194// Use of this source code is governed by a BSD-style license that can be 195// found in the LICENSE file. 196 197package ${PACKAGE}; 198 199// This file is autogenerated by 200// base/android/jni_generator/jni_registration_generator.py 201// Please do not change its content. 202 203public class ${CLASS_NAME} { 204 public static final boolean TESTING_ENABLED = ${TESTING_ENABLED}; 205 public static final boolean REQUIRE_MOCK = ${REQUIRE_MOCK}; 206${SIGNATURES} 207 208} 209""") 210 211 return template.substitute({ 212 'TESTING_ENABLED': 213 str(proxy_opts.enable_mocks).lower(), 214 'REQUIRE_MOCK': 215 str(proxy_opts.require_mocks).lower(), 216 'CLASS_NAME': 217 jni_generator.ProxyHelpers.GetClass(proxy_opts.use_hash), 218 'PACKAGE': 219 jni_generator.ProxyHelpers.GetPackage(proxy_opts.use_hash).replace( 220 '/', '.'), 221 'SIGNATURES': 222 registration_dict['PROXY_NATIVE_SIGNATURES'] 223 }) 224 225 226def CreateFromDict(registration_dict, use_hash): 227 """Returns the content of the header file.""" 228 229 template = string.Template("""\ 230// Copyright 2017 The Chromium Authors. All rights reserved. 231// Use of this source code is governed by a BSD-style license that can be 232// found in the LICENSE file. 233 234 235// This file is autogenerated by 236// base/android/jni_generator/jni_registration_generator.py 237// Please do not change its content. 238 239#ifndef ${HEADER_GUARD} 240#define ${HEADER_GUARD} 241 242#include <jni.h> 243 244#include "base/android/jni_generator/jni_generator_helper.h" 245#include "base/android/jni_int_wrapper.h" 246#include "base/stl_util.h" // For base::size(). 247 248 249// Step 1: Forward declarations (classes). 250${CLASS_PATH_DECLARATIONS} 251 252// Step 2: Forward declarations (methods). 253 254${FORWARD_DECLARATIONS} 255 256// Step 3: Method declarations. 257 258${JNI_NATIVE_METHOD_ARRAY}\ 259${PROXY_NATIVE_METHOD_ARRAY}\ 260 261${JNI_NATIVE_METHOD} 262// Step 4: Main dex and non-main dex registration functions. 263 264namespace ${NAMESPACE} { 265 266bool RegisterMainDexNatives(JNIEnv* env) {\ 267${REGISTER_MAIN_DEX_PROXY_NATIVES} 268${REGISTER_MAIN_DEX_NATIVES} 269 return true; 270} 271 272bool RegisterNonMainDexNatives(JNIEnv* env) {\ 273${REGISTER_PROXY_NATIVES} 274${REGISTER_NON_MAIN_DEX_NATIVES} 275 return true; 276} 277 278} // namespace ${NAMESPACE} 279 280#endif // ${HEADER_GUARD} 281""") 282 _SetProxyRegistrationFields(registration_dict, use_hash) 283 284 if len(registration_dict['FORWARD_DECLARATIONS']) == 0: 285 return '' 286 287 return template.substitute(registration_dict) 288 289 290class HeaderGenerator(object): 291 """Generates an inline header file for JNI registration.""" 292 293 def __init__(self, namespace, fully_qualified_class, natives, jni_params, 294 main_dex, use_proxy_hash): 295 self.namespace = namespace 296 self.natives = natives 297 self.proxy_natives = [n for n in natives if n.is_proxy] 298 self.non_proxy_natives = [n for n in natives if not n.is_proxy] 299 self.fully_qualified_class = fully_qualified_class 300 self.jni_params = jni_params 301 self.class_name = self.fully_qualified_class.split('/')[-1] 302 self.main_dex = main_dex 303 self.helper = jni_generator.HeaderFileGeneratorHelper( 304 self.class_name, fully_qualified_class, use_proxy_hash) 305 self.use_proxy_hash = use_proxy_hash 306 self.registration_dict = None 307 308 def Generate(self): 309 self.registration_dict = {'FULL_CLASS_NAME': self.fully_qualified_class} 310 self._AddClassPathDeclarations() 311 self._AddForwardDeclaration() 312 self._AddJNINativeMethodsArrays() 313 self._AddProxySignatures() 314 self._AddProxyNativeMethodKStrings() 315 self._AddRegisterNativesCalls() 316 self._AddRegisterNativesFunctions() 317 return self.registration_dict 318 319 def _SetDictValue(self, key, value): 320 self.registration_dict[key] = jni_generator.WrapOutput(value) 321 322 def _AddClassPathDeclarations(self): 323 classes = self.helper.GetUniqueClasses(self.natives) 324 self._SetDictValue( 325 'CLASS_PATH_DECLARATIONS', 326 self.helper.GetClassPathLines(classes, declare_only=True)) 327 328 def _AddForwardDeclaration(self): 329 """Add the content of the forward declaration to the dictionary.""" 330 template = string.Template("""\ 331JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}( 332 JNIEnv* env, 333 ${PARAMS_IN_STUB}); 334""") 335 forward_declaration = '' 336 for native in self.natives: 337 value = { 338 'RETURN': jni_generator.JavaDataTypeToC(native.return_type), 339 'STUB_NAME': self.helper.GetStubName(native), 340 'PARAMS_IN_STUB': jni_generator.GetParamsInStub(native), 341 } 342 forward_declaration += template.substitute(value) 343 self._SetDictValue('FORWARD_DECLARATIONS', forward_declaration) 344 345 def _AddRegisterNativesCalls(self): 346 """Add the body of the RegisterNativesImpl method to the dictionary.""" 347 348 # Only register if there is at least 1 non-proxy native 349 if len(self.non_proxy_natives) == 0: 350 return '' 351 352 template = string.Template("""\ 353 if (!${REGISTER_NAME}(env)) 354 return false; 355""") 356 value = { 357 'REGISTER_NAME': 358 jni_generator.GetRegistrationFunctionName(self.fully_qualified_class) 359 } 360 register_body = template.substitute(value) 361 if self.main_dex: 362 self._SetDictValue('REGISTER_MAIN_DEX_NATIVES', register_body) 363 else: 364 self._SetDictValue('REGISTER_NON_MAIN_DEX_NATIVES', register_body) 365 366 def _AddJNINativeMethodsArrays(self): 367 """Returns the implementation of the array of native methods.""" 368 template = string.Template("""\ 369static const JNINativeMethod kMethods_${JAVA_CLASS}[] = { 370${KMETHODS} 371}; 372 373""") 374 open_namespace = '' 375 close_namespace = '' 376 if self.namespace: 377 parts = self.namespace.split('::') 378 all_namespaces = ['namespace %s {' % ns for ns in parts] 379 open_namespace = '\n'.join(all_namespaces) + '\n' 380 all_namespaces = ['} // namespace %s' % ns for ns in parts] 381 all_namespaces.reverse() 382 close_namespace = '\n'.join(all_namespaces) + '\n\n' 383 384 body = self._SubstituteNativeMethods(template) 385 self._SetDictValue('JNI_NATIVE_METHOD_ARRAY', ''.join((open_namespace, body, 386 close_namespace))) 387 388 def _GetKMethodsString(self, clazz): 389 ret = [] 390 for native in self.non_proxy_natives: 391 if (native.java_class_name == clazz 392 or (not native.java_class_name and clazz == self.class_name)): 393 ret += [self._GetKMethodArrayEntry(native)] 394 return '\n'.join(ret) 395 396 def _GetKMethodArrayEntry(self, native): 397 template = string.Template(' { "${NAME}", ${JNI_SIGNATURE}, ' + 398 'reinterpret_cast<void*>(${STUB_NAME}) },') 399 400 name = 'native' + native.name 401 if native.is_proxy: 402 # Literal name of the native method in the class that contains the actual 403 # native declaration. 404 name = native.proxy_name 405 values = { 406 'NAME': 407 name, 408 'JNI_SIGNATURE': 409 self.jni_params.Signature(native.params, native.return_type), 410 'STUB_NAME': 411 self.helper.GetStubName(native) 412 } 413 return template.substitute(values) 414 415 def _AddProxySignatures(self): 416 self.registration_dict['PROXY_NATIVE_SIGNATURES'] = ('\n'.join( 417 _MakeProxySignature(n) for n in self.proxy_natives)) 418 419 def _AddProxyNativeMethodKStrings(self): 420 """Returns KMethodString for wrapped native methods in all_classes """ 421 422 if self.main_dex: 423 key = 'PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX' 424 else: 425 key = 'PROXY_NATIVE_METHOD_ARRAY' 426 427 proxy_k_strings = ('\n'.join( 428 self._GetKMethodArrayEntry(p) for p in self.proxy_natives)) 429 430 self._SetDictValue(key, proxy_k_strings) 431 432 def _SubstituteNativeMethods(self, template, sub_proxy=False): 433 """Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided 434 template.""" 435 ret = [] 436 all_classes = self.helper.GetUniqueClasses(self.natives) 437 all_classes[self.class_name] = self.fully_qualified_class 438 439 for clazz, full_clazz in all_classes.items(): 440 if not sub_proxy: 441 if clazz == jni_generator.ProxyHelpers.GetClass(self.use_proxy_hash): 442 continue 443 444 kmethods = self._GetKMethodsString(clazz) 445 namespace_str = '' 446 if self.namespace: 447 namespace_str = self.namespace + '::' 448 if kmethods: 449 values = { 450 'NAMESPACE': namespace_str, 451 'JAVA_CLASS': jni_generator.EscapeClassName(full_clazz), 452 'KMETHODS': kmethods 453 } 454 ret += [template.substitute(values)] 455 if not ret: return '' 456 return '\n'.join(ret) 457 458 def GetJNINativeMethodsString(self): 459 """Returns the implementation of the array of native methods.""" 460 template = string.Template("""\ 461static const JNINativeMethod kMethods_${JAVA_CLASS}[] = { 462${KMETHODS} 463 464}; 465""") 466 return self._SubstituteNativeMethods(template) 467 468 def _AddRegisterNativesFunctions(self): 469 """Returns the code for RegisterNatives.""" 470 natives = self._GetRegisterNativesImplString() 471 if not natives: 472 return '' 473 template = string.Template("""\ 474JNI_REGISTRATION_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) { 475${NATIVES}\ 476 return true; 477} 478 479""") 480 values = { 481 'REGISTER_NAME': 482 jni_generator.GetRegistrationFunctionName(self.fully_qualified_class), 483 'NATIVES': 484 natives 485 } 486 self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values)) 487 488 def _GetRegisterNativesImplString(self): 489 """Returns the shared implementation for RegisterNatives.""" 490 template = string.Template("""\ 491 const int kMethods_${JAVA_CLASS}Size = 492 base::size(${NAMESPACE}kMethods_${JAVA_CLASS}); 493 if (env->RegisterNatives( 494 ${JAVA_CLASS}_clazz(env), 495 ${NAMESPACE}kMethods_${JAVA_CLASS}, 496 kMethods_${JAVA_CLASS}Size) < 0) { 497 jni_generator::HandleRegistrationError(env, 498 ${JAVA_CLASS}_clazz(env), 499 __FILE__); 500 return false; 501 } 502 503""") 504 # Only register if there is a native method not in a proxy, 505 # since all the proxies will be registered together. 506 if len(self.non_proxy_natives) != 0: 507 return self._SubstituteNativeMethods(template) 508 return '' 509 510 511def _MakeProxySignature(proxy_native): 512 signature_template = string.Template(""" 513 public static native ${RETURN_TYPE} ${NAME}(${PARAMS});""") 514 515 return signature_template.substitute({ 516 'RETURN_TYPE': 517 proxy_native.return_type, 518 'NAME': 519 proxy_native.proxy_name, 520 'PARAMS': 521 jni_generator.JniParams.MakeProxyParamSignature(proxy_native.params) 522 }) 523 524 525class ProxyOptions: 526 527 def __init__(self, **kwargs): 528 self.use_hash = kwargs.get('use_hash', False) 529 self.enable_mocks = kwargs.get('enable_mocks', False) 530 self.require_mocks = kwargs.get('require_mocks', False) 531 # Can never require and disable. 532 assert self.enable_mocks or not self.require_mocks 533 534 535def main(argv): 536 arg_parser = argparse.ArgumentParser() 537 build_utils.AddDepfileOption(arg_parser) 538 539 arg_parser.add_argument( 540 '--sources-files', 541 required=True, 542 action='append', 543 help='A list of .sources files which contain Java ' 544 'file paths.') 545 arg_parser.add_argument( 546 '--header-path', help='Path to output header file (optional).') 547 arg_parser.add_argument( 548 '--srcjar-path', 549 required=True, 550 help='Path to output srcjar for GEN_JNI.java (Or J/N.java if proxy' 551 ' hash is enabled).') 552 arg_parser.add_argument( 553 '--sources-exclusions', 554 default=[], 555 help='A list of Java files which should be ignored ' 556 'by the parser.') 557 arg_parser.add_argument( 558 '--namespace', 559 default='', 560 help='Namespace to wrap the registration functions ' 561 'into.') 562 # TODO(crbug.com/898261) hook these flags up to the build config to enable 563 # mocking in instrumentation tests 564 arg_parser.add_argument( 565 '--enable_proxy_mocks', 566 default=False, 567 action='store_true', 568 help='Allows proxy native impls to be mocked through Java.') 569 arg_parser.add_argument( 570 '--require_mocks', 571 default=False, 572 action='store_true', 573 help='Requires all used native implementations to have a mock set when ' 574 'called. Otherwise an exception will be thrown.') 575 arg_parser.add_argument( 576 '--use_proxy_hash', 577 action='store_true', 578 help='Enables hashing of the native declaration for methods in ' 579 'an @JniNatives interface') 580 args = arg_parser.parse_args(build_utils.ExpandFileArgs(argv[1:])) 581 582 if not args.enable_proxy_mocks and args.require_mocks: 583 arg_parser.error( 584 'Invalid arguments: --require_mocks without --enable_proxy_mocks. ' 585 'Cannot require mocks if they are not enabled.') 586 587 sources_files = sorted(set(build_utils.ParseGnList(args.sources_files))) 588 proxy_opts = ProxyOptions( 589 use_hash=args.use_proxy_hash, 590 require_mocks=args.require_mocks, 591 enable_mocks=args.enable_proxy_mocks) 592 593 java_file_paths = [] 594 for f in sources_files: 595 # Skip generated files, since the GN targets do not declare any deps. 596 java_file_paths.extend( 597 p for p in build_utils.ReadSourcesList(f) 598 if p.startswith('..') and p not in args.sources_exclusions) 599 _Generate( 600 java_file_paths, 601 args.srcjar_path, 602 proxy_opts=proxy_opts, 603 header_path=args.header_path, 604 namespace=args.namespace) 605 606 if args.depfile: 607 build_utils.WriteDepfile( 608 args.depfile, 609 args.srcjar_path, 610 sources_files + java_file_paths, 611 add_pydeps=False) 612 613 614if __name__ == '__main__': 615 sys.exit(main(sys.argv)) 616