1#!/usr/bin/env python3 2# Copyright 2012 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Extracts native methods from a Java file and generates the JNI bindings. 7If you change this, please run and update the tests.""" 8import argparse 9import base64 10import collections 11import hashlib 12import os 13import re 14import shutil 15from string import Template 16import subprocess 17import sys 18import tempfile 19import textwrap 20import zipfile 21 22_FILE_DIR = os.path.dirname(__file__) 23_CHROMIUM_SRC = os.path.join(_FILE_DIR, os.pardir, os.pardir, os.pardir) 24_BUILD_ANDROID_GYP = os.path.join(_CHROMIUM_SRC, 'build', 'android', 'gyp') 25 26# Item 0 of sys.path is the directory of the main file; item 1 is PYTHONPATH 27# (if set); item 2 is system libraries. 28sys.path.insert(1, _BUILD_ANDROID_GYP) 29 30from util import build_utils 31import action_helpers # build_utils adds //build to sys.path. 32 33# Match single line comments, multiline comments, character literals, and 34# double-quoted strings. 35_COMMENT_REMOVER_REGEX = re.compile( 36 r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', 37 re.DOTALL | re.MULTILINE) 38 39_EXTRACT_NATIVES_REGEX = re.compile( 40 r'(@NativeClassQualifiedName' 41 r'\(\"(?P<native_class_name>.*?)\"\)\s+)?' 42 r'(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?' 43 r'(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native ' 44 r'(?P<return_type>\S*) ' 45 r'(?P<name>native\w+)\((?P<params>.*?)\);') 46 47_MAIN_DEX_REGEX = re.compile(r'^\s*(?:@(?:\w+\.)*\w+\s+)*@MainDex\b', 48 re.MULTILINE) 49 50# Matches on method declarations unlike _EXTRACT_NATIVES_REGEX 51# doesn't require name to be prefixed with native, and does not 52# require a native qualifier. 53_EXTRACT_METHODS_REGEX = re.compile( 54 r'(@NativeClassQualifiedName' 55 r'\(\"(?P<native_class_name>.*?)\"\)\s*)?' 56 r'(?P<qualifiers>' 57 r'((public|private|static|final|abstract|protected|native)\s*)*)\s+' 58 r'(?P<return_type>\S*)\s+' 59 r'(?P<name>\w+)\((?P<params>.*?)\);', 60 flags=re.DOTALL) 61 62_NATIVE_PROXY_EXTRACTION_REGEX = re.compile( 63 r'@NativeMethods(?:\(\s*"(?P<module_name>\w+)"\s*\))?[\S\s]+?interface\s*' 64 r'(?P<interface_name>\w*)\s*(?P<interface_body>{(\s*.*)+?\s*})') 65 66# Use 100 columns rather than 80 because it makes many lines more readable. 67_WRAP_LINE_LENGTH = 100 68# WrapOutput() is fairly slow. Pre-creating TextWrappers helps a bit. 69_WRAPPERS_BY_INDENT = [ 70 textwrap.TextWrapper( 71 width=_WRAP_LINE_LENGTH, 72 expand_tabs=False, 73 replace_whitespace=False, 74 subsequent_indent=' ' * (indent + 4), 75 break_long_words=False) for indent in range(50) 76] # 50 chosen experimentally. 77 78JAVA_POD_TYPE_MAP = { 79 'int': 'jint', 80 'byte': 'jbyte', 81 'char': 'jchar', 82 'short': 'jshort', 83 'boolean': 'jboolean', 84 'long': 'jlong', 85 'double': 'jdouble', 86 'float': 'jfloat', 87} 88 89JAVA_TYPE_MAP = { 90 'void': 'void', 91 'String': 'jstring', 92 'Class': 'jclass', 93 'Throwable': 'jthrowable', 94 'java/lang/String': 'jstring', 95 'java/lang/Class': 'jclass', 96 'java/lang/Throwable': 'jthrowable', 97} 98 99 100class ParseError(Exception): 101 """Exception thrown when we can't parse the input file.""" 102 103 def __init__(self, description, *context_lines): 104 Exception.__init__(self) 105 self.description = description 106 self.context_lines = context_lines 107 108 def __str__(self): 109 context = '\n'.join(self.context_lines) 110 return '***\nERROR: %s\n\n%s\n***' % (self.description, context) 111 112 113class Param(object): 114 """Describes a param for a method, either java or native.""" 115 116 def __init__(self, **kwargs): 117 self.annotations = kwargs.get('annotations', []) 118 self.datatype = kwargs['datatype'] 119 self.name = kwargs['name'] 120 121 122class NativeMethod(object): 123 """Describes a C/C++ method that is called by Java code""" 124 125 def __init__(self, **kwargs): 126 self.static = kwargs['static'] 127 self.java_class_name = kwargs['java_class_name'] 128 self.return_type = kwargs['return_type'] 129 self.params = kwargs['params'] 130 self.is_proxy = kwargs.get('is_proxy', False) 131 132 self.name = kwargs['name'] 133 if self.is_proxy: 134 # Proxy methods don't have a native prefix so the first letter is 135 # lowercase. But we still want the CPP declaration to use upper camel 136 # case for the method name. 137 self.name = self.name[0].upper() + self.name[1:] 138 139 self.proxy_name = kwargs.get('proxy_name', self.name) 140 self.hashed_proxy_name = kwargs.get('hashed_proxy_name', None) 141 self.switch_num = None 142 143 if self.params: 144 assert type(self.params) is list 145 assert type(self.params[0]) is Param 146 147 ptr_type = kwargs.get('ptr_type', 'int') 148 if (self.params and self.params[0].datatype == ptr_type 149 and self.params[0].name.startswith('native')): 150 self.ptr_type = ptr_type 151 self.type = 'method' 152 self.p0_type = kwargs.get('p0_type') 153 if self.p0_type is None: 154 self.p0_type = self.params[0].name[len('native'):] 155 if kwargs.get('native_class_name'): 156 self.p0_type = kwargs['native_class_name'] 157 else: 158 self.type = 'function' 159 self.method_id_var_name = kwargs.get('method_id_var_name', None) 160 self.return_and_signature = (self.return_type, 161 tuple(p.datatype for p in self.params)) 162 163 164class CalledByNative(object): 165 """Describes a java method exported to c/c++""" 166 167 def __init__(self, **kwargs): 168 self.system_class = kwargs['system_class'] 169 self.unchecked = kwargs['unchecked'] 170 self.static = kwargs['static'] 171 self.java_class_name = kwargs['java_class_name'] 172 self.return_type = kwargs['return_type'] 173 self.name = kwargs['name'] 174 self.params = kwargs['params'] 175 self.method_id_var_name = kwargs.get('method_id_var_name', None) 176 self.signature = kwargs.get('signature') 177 self.is_constructor = kwargs.get('is_constructor', False) 178 self.env_call = GetEnvCall(self.is_constructor, self.static, 179 self.return_type) 180 self.static_cast = GetStaticCastForReturnType(self.return_type) 181 182 183class ConstantField(object): 184 185 def __init__(self, **kwargs): 186 self.name = kwargs['name'] 187 self.value = kwargs['value'] 188 189 190def JavaDataTypeToC(java_type): 191 """Returns a C datatype for the given java type.""" 192 java_type = _StripGenerics(java_type) 193 if java_type in JAVA_POD_TYPE_MAP: 194 return JAVA_POD_TYPE_MAP[java_type] 195 elif java_type in JAVA_TYPE_MAP: 196 return JAVA_TYPE_MAP[java_type] 197 elif java_type.endswith('[]'): 198 if java_type[:-2] in JAVA_POD_TYPE_MAP: 199 return JAVA_POD_TYPE_MAP[java_type[:-2]] + 'Array' 200 return 'jobjectArray' 201 else: 202 return 'jobject' 203 204 205def JavaTypeToProxyCast(java_type): 206 """Maps from a java type to the type used by the native proxy GEN_JNI class""" 207 # All the types and array types of JAVA_TYPE_MAP become jobjectArray across 208 # jni but they still need to be passed as the original type on the java side. 209 raw_type = java_type.rstrip('[]') 210 if raw_type in JAVA_POD_TYPE_MAP or raw_type in JAVA_TYPE_MAP: 211 return java_type 212 213 # All other types should just be passed as Objects or Object arrays. 214 return 'Object' + java_type[len(raw_type):] 215 216 217def WrapCTypeForDeclaration(c_type): 218 """Wrap the C datatype in a JavaRef if required.""" 219 if re.match(RE_SCOPED_JNI_TYPES, c_type): 220 return 'const base::android::JavaParamRef<' + c_type + '>&' 221 else: 222 return c_type 223 224 225def _JavaDataTypeToCForDeclaration(java_type): 226 """Returns a JavaRef-wrapped C datatype for the given java type.""" 227 return WrapCTypeForDeclaration(JavaDataTypeToC(java_type)) 228 229 230def JavaDataTypeToCForCalledByNativeParam(java_type): 231 """Returns a C datatype to be when calling from native.""" 232 if java_type == 'int': 233 return 'JniIntWrapper' 234 else: 235 c_type = JavaDataTypeToC(java_type) 236 if re.match(RE_SCOPED_JNI_TYPES, c_type): 237 return 'const base::android::JavaRef<' + c_type + '>&' 238 else: 239 return c_type 240 241 242def JavaReturnValueToC(java_type): 243 """Returns a valid C return value for the given java type.""" 244 java_pod_type_map = { 245 'int': '0', 246 'byte': '0', 247 'char': '0', 248 'short': '0', 249 'boolean': 'false', 250 'long': '0', 251 'double': '0', 252 'float': '0', 253 'void': '' 254 } 255 return java_pod_type_map.get(java_type, 'NULL') 256 257 258def _GetJNIFirstParam(native, for_declaration): 259 c_type = 'jclass' if native.static else 'jobject' 260 261 if for_declaration: 262 c_type = WrapCTypeForDeclaration(c_type) 263 return [c_type + ' jcaller'] 264 265 266def GetFullyQualifiedClassWithPackagePrefix(fully_qualified_class, 267 package_prefix): 268 if package_prefix: 269 return '%s/%s' % (package_prefix.replace(".", "/"), fully_qualified_class) 270 return fully_qualified_class 271 272 273def _GetParamsInDeclaration(native): 274 """Returns the params for the forward declaration. 275 276 Args: 277 native: the native dictionary describing the method. 278 279 Returns: 280 A string containing the params. 281 """ 282 if not native.static: 283 return _GetJNIFirstParam(native, True) + [ 284 _JavaDataTypeToCForDeclaration(param.datatype) + ' ' + param.name 285 for param in native.params 286 ] 287 return [ 288 _JavaDataTypeToCForDeclaration(param.datatype) + ' ' + param.name 289 for param in native.params 290 ] 291 292 293def GetParamsInStub(native): 294 """Returns the params for the stub declaration. 295 296 Args: 297 native: the native dictionary describing the method. 298 299 Returns: 300 A string containing the params. 301 """ 302 params = [JavaDataTypeToC(p.datatype) + ' ' + p.name for p in native.params] 303 params = _GetJNIFirstParam(native, False) + params 304 return ',\n '.join(params) 305 306 307def _StripGenerics(value): 308 """Strips Java generics from a string.""" 309 nest_level = 0 # How deeply we are nested inside the generics. 310 start_index = 0 # Starting index of the last non-generic region. 311 out = [] 312 313 for i, c in enumerate(value): 314 if c == '<': 315 if nest_level == 0: 316 out.append(value[start_index:i]) 317 nest_level += 1 318 elif c == '>': 319 start_index = i + 1 320 nest_level -= 1 321 out.append(value[start_index:]) 322 323 return ''.join(out) 324 325 326def _NameIsTestOnly(name): 327 return name.endswith('ForTest') or name.endswith('ForTesting') 328 329 330class JniParams(object): 331 """Get JNI related parameters.""" 332 333 def __init__(self, fully_qualified_class): 334 self._fully_qualified_class = 'L' + fully_qualified_class 335 self._package = '/'.join(fully_qualified_class.split('/')[:-1]) 336 self._imports = [] 337 self._inner_classes = [] 338 self._implicit_imports = [] 339 340 def ExtractImportsAndInnerClasses(self, contents): 341 contents = contents.replace('\n', '') 342 re_import = re.compile(r'import.*?(?P<class>\S*?);') 343 for match in re.finditer(re_import, contents): 344 self._imports += ['L' + match.group('class').replace('.', '/')] 345 346 re_inner = re.compile(r'(class|interface|enum)\s+?(?P<name>\w+?)\W') 347 for match in re.finditer(re_inner, contents): 348 inner = match.group('name') 349 if not self._fully_qualified_class.endswith(inner): 350 self._inner_classes += [self._fully_qualified_class + '$' + inner] 351 352 re_additional_imports = re.compile( 353 r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)') 354 for match in re.finditer(re_additional_imports, contents): 355 for class_name in match.group('class_names').split(','): 356 self._AddAdditionalImport(class_name.strip()) 357 358 def JavaToJni(self, param): 359 """Converts a java param into a JNI signature type.""" 360 pod_param_map = { 361 'int': 'I', 362 'boolean': 'Z', 363 'char': 'C', 364 'short': 'S', 365 'long': 'J', 366 'double': 'D', 367 'float': 'F', 368 'byte': 'B', 369 'void': 'V', 370 } 371 object_param_list = [ 372 'Ljava/lang/Boolean', 373 'Ljava/lang/Integer', 374 'Ljava/lang/Long', 375 'Ljava/lang/Object', 376 'Ljava/lang/String', 377 'Ljava/lang/Class', 378 'Ljava/lang/ClassLoader', 379 'Ljava/lang/CharSequence', 380 'Ljava/lang/Runnable', 381 'Ljava/lang/Throwable', 382 ] 383 384 prefix = '' 385 # Array? 386 while param[-2:] == '[]': 387 prefix += '[' 388 param = param[:-2] 389 # Generic? 390 if '<' in param: 391 param = param[:param.index('<')] 392 if param in pod_param_map: 393 return prefix + pod_param_map[param] 394 if '/' in param: 395 # Coming from javap, use the fully qualified param directly. 396 return prefix + 'L' + param + ';' 397 398 for qualified_name in (object_param_list + [self._fully_qualified_class] + 399 self._inner_classes): 400 if (qualified_name.endswith('/' + param) 401 or qualified_name.endswith('$' + param.replace('.', '$')) 402 or qualified_name == 'L' + param): 403 return prefix + qualified_name + ';' 404 405 # Is it from an import? (e.g. referecing Class from import pkg.Class; 406 # note that referencing an inner class Inner from import pkg.Class.Inner 407 # is not supported). 408 for qualified_name in self._imports: 409 if qualified_name.endswith('/' + param): 410 # Ensure it's not an inner class. 411 components = qualified_name.split('/') 412 if len(components) > 2 and components[-2][0].isupper(): 413 raise SyntaxError( 414 'Inner class (%s) can not be imported ' 415 'and used by JNI (%s). Please import the outer ' 416 'class and use Outer.Inner instead.' % (qualified_name, param)) 417 return prefix + qualified_name + ';' 418 419 # Is it an inner class from an outer class import? (e.g. referencing 420 # Class.Inner from import pkg.Class). 421 if '.' in param: 422 components = param.split('.') 423 outer = '/'.join(components[:-1]) 424 inner = components[-1] 425 for qualified_name in self._imports: 426 if qualified_name.endswith('/' + outer): 427 return (prefix + qualified_name + '$' + inner + ';') 428 raise SyntaxError('Inner class (%s) can not be ' 429 'used directly by JNI. Please import the outer ' 430 'class, probably:\n' 431 'import %s.%s;' % (param, self._package.replace( 432 '/', '.'), outer.replace('/', '.'))) 433 434 self._CheckImplicitImports(param) 435 436 # Type not found, falling back to same package as this class. 437 return (prefix + 'L' + self._package + '/' + param + ';') 438 439 def _AddAdditionalImport(self, class_name): 440 assert class_name.endswith('.class') 441 raw_class_name = class_name[:-len('.class')] 442 if '.' in raw_class_name: 443 raise SyntaxError('%s cannot be used in @JNIAdditionalImport. ' 444 'Only import unqualified outer classes.' % class_name) 445 new_import = 'L%s/%s' % (self._package, raw_class_name) 446 if new_import in self._imports: 447 raise SyntaxError('Do not use JNIAdditionalImport on an already ' 448 'imported class: %s' % (new_import.replace('/', '.'))) 449 self._imports += [new_import] 450 451 def _CheckImplicitImports(self, param): 452 # Ensure implicit imports, such as java.lang.*, are not being treated 453 # as being in the same package. 454 if not self._implicit_imports: 455 # This file was generated from android.jar and lists 456 # all classes that are implicitly imported. 457 android_jar_path = os.path.join(_FILE_DIR, 'android_jar.classes') 458 with open(android_jar_path) as f: 459 self._implicit_imports = f.readlines() 460 for implicit_import in self._implicit_imports: 461 implicit_import = implicit_import.strip().replace('.class', '') 462 implicit_import = implicit_import.replace('/', '.') 463 if implicit_import.endswith('.' + param): 464 raise SyntaxError('Ambiguous class (%s) can not be used directly ' 465 'by JNI.\nPlease import it, probably:\n\n' 466 'import %s;' % (param, implicit_import)) 467 468 def Signature(self, params, returns): 469 """Returns the JNI signature for the given datatypes.""" 470 items = ['('] 471 items += [self.JavaToJni(param.datatype) for param in params] 472 items += [')'] 473 items += [self.JavaToJni(returns)] 474 return '"{}"'.format(''.join(items)) 475 476 @staticmethod 477 def ParseJavaPSignature(signature_line): 478 prefix = 'Signature: ' 479 index = signature_line.find(prefix) 480 if index == -1: 481 prefix = 'descriptor: ' 482 index = signature_line.index(prefix) 483 return '"%s"' % signature_line[index + len(prefix):] 484 485 @staticmethod 486 def Parse(params, use_proxy_types=False, from_javap=False): 487 """Parses the params into a list of Param objects.""" 488 if not params: 489 return [] 490 ret = [] 491 params = _StripGenerics(params) 492 for p in params.split(','): 493 items = p.split() 494 495 if 'final' in items: 496 items.remove('final') 497 498 # Remove @Annotations from parameters. 499 annotations = [] 500 while items[0].startswith('@'): 501 annotations.append(items[0]) 502 del items[0] 503 504 param = Param( 505 annotations=annotations, 506 datatype=items[0], 507 name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), 508 ) 509 # Handle varargs. 510 if param.datatype.endswith('...'): 511 param.datatype = param.datatype[:-3] + '[]' 512 513 if from_javap: 514 param.datatype = param.datatype.replace('.', '/') 515 516 if use_proxy_types: 517 param.datatype = JavaTypeToProxyCast(param.datatype) 518 519 ret += [param] 520 return ret 521 522 523def ExtractJNINamespace(contents): 524 re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') 525 m = re.findall(re_jni_namespace, contents) 526 if not m: 527 return '' 528 return m[0] 529 530 531def ExtractFullyQualifiedJavaClassName(file_name, contents): 532 assert not file_name.endswith('.kt'), ( 533 f'Found {file_name}, but Kotlin is not supported by JNI generator.') 534 re_package = re.compile('.*?package (.*?);') 535 matches = re.findall(re_package, contents) 536 if not matches: 537 raise SyntaxError('Unable to find "package" line in %s' % file_name) 538 class_path = matches[0].replace('.', '/') 539 class_name = os.path.splitext(os.path.basename(file_name))[0] 540 return class_path + '/' + class_name 541 542 543def ExtractNatives(contents, ptr_type): 544 """Returns a list of dict containing information about a native method.""" 545 contents = contents.replace('\n', '') 546 natives = [] 547 for match in _EXTRACT_NATIVES_REGEX.finditer(contents): 548 native = NativeMethod( 549 static='static' in match.group('qualifiers'), 550 java_class_name=match.group('java_class_name'), 551 native_class_name=match.group('native_class_name'), 552 return_type=match.group('return_type'), 553 name=match.group('name').replace('native', ''), 554 params=JniParams.Parse(match.group('params')), 555 ptr_type=ptr_type) 556 natives += [native] 557 return natives 558 559 560def IsMainDexJavaClass(contents): 561 """Returns True if the class or any of its methods are annotated as @MainDex. 562 563 JNI registration doesn't always need to be completed for non-browser processes 564 since most Java code is only used by the browser process. Classes that are 565 needed by non-browser processes must explicitly be annotated with @MainDex 566 to force JNI registration. 567 """ 568 return bool(_MAIN_DEX_REGEX.search(contents)) 569 570 571def EscapeClassName(fully_qualified_class): 572 """Returns an escaped string concatenating the Java package and class.""" 573 escaped = fully_qualified_class.replace('_', '_1') 574 return escaped.replace('/', '_').replace('$', '_00024') 575 576 577def GetRegistrationFunctionName(fully_qualified_class): 578 """Returns the register name with a given class.""" 579 return 'RegisterNative_' + EscapeClassName(fully_qualified_class) 580 581 582def GetStaticCastForReturnType(return_type): 583 type_map = { 584 'String': 'jstring', 585 'java/lang/String': 'jstring', 586 'Class': 'jclass', 587 'java/lang/Class': 'jclass', 588 'Throwable': 'jthrowable', 589 'java/lang/Throwable': 'jthrowable', 590 'boolean[]': 'jbooleanArray', 591 'byte[]': 'jbyteArray', 592 'char[]': 'jcharArray', 593 'short[]': 'jshortArray', 594 'int[]': 'jintArray', 595 'long[]': 'jlongArray', 596 'float[]': 'jfloatArray', 597 'double[]': 'jdoubleArray' 598 } 599 return_type = _StripGenerics(return_type) 600 ret = type_map.get(return_type, None) 601 if ret: 602 return ret 603 if return_type.endswith('[]'): 604 return 'jobjectArray' 605 return None 606 607 608def GetEnvCall(is_constructor, is_static, return_type): 609 """Maps the types availabe via env->Call__Method.""" 610 if is_constructor: 611 return 'NewObject' 612 env_call_map = { 613 'boolean': 'Boolean', 614 'byte': 'Byte', 615 'char': 'Char', 616 'short': 'Short', 617 'int': 'Int', 618 'long': 'Long', 619 'float': 'Float', 620 'void': 'Void', 621 'double': 'Double', 622 'Object': 'Object', 623 } 624 call = env_call_map.get(return_type, 'Object') 625 if is_static: 626 call = 'Static' + call 627 return 'Call' + call + 'Method' 628 629 630def GetMangledParam(datatype): 631 """Returns a mangled identifier for the datatype.""" 632 if len(datatype) <= 2: 633 return datatype.replace('[', 'A') 634 ret = '' 635 for i in range(1, len(datatype)): 636 c = datatype[i] 637 if c == '[': 638 ret += 'A' 639 elif c.isupper() or datatype[i - 1] in ['/', 'L']: 640 ret += c.upper() 641 return ret 642 643 644def GetMangledMethodName(jni_params, name, params, return_type): 645 """Returns a mangled method name for the given signature. 646 647 The returned name can be used as a C identifier and will be unique for all 648 valid overloads of the same method. 649 650 Args: 651 jni_params: JniParams object. 652 name: string. 653 params: list of Param. 654 return_type: string. 655 656 Returns: 657 A mangled name. 658 """ 659 mangled_items = [] 660 for datatype in [return_type] + [x.datatype for x in params]: 661 mangled_items += [GetMangledParam(jni_params.JavaToJni(datatype))] 662 mangled_name = name + '_'.join(mangled_items) 663 assert re.match(r'[0-9a-zA-Z_]+', mangled_name) 664 return mangled_name 665 666 667def MangleCalledByNatives(jni_params, called_by_natives, always_mangle): 668 """Mangles all the overloads from the call_by_natives list or 669 mangle all methods if always_mangle is true. 670 """ 671 method_counts = collections.defaultdict( 672 lambda: collections.defaultdict(lambda: 0)) 673 for called_by_native in called_by_natives: 674 java_class_name = called_by_native.java_class_name 675 name = called_by_native.name 676 method_counts[java_class_name][name] += 1 677 for called_by_native in called_by_natives: 678 java_class_name = called_by_native.java_class_name 679 method_name = called_by_native.name 680 method_id_var_name = method_name 681 if always_mangle or method_counts[java_class_name][method_name] > 1: 682 method_id_var_name = GetMangledMethodName(jni_params, method_name, 683 called_by_native.params, 684 called_by_native.return_type) 685 called_by_native.method_id_var_name = method_id_var_name 686 return called_by_natives 687 688 689# Regex to match the JNI types that should be wrapped in a JavaRef. 690RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*Array') 691 692# Regex to match a string like "@CalledByNative public void foo(int bar)". 693RE_CALLED_BY_NATIVE = re.compile( 694 r'@CalledByNative((?P<Unchecked>(?:Unchecked)?|ForTesting))' 695 r'(?:\("(?P<annotation>.*)"\))?' 696 r'(?:\s+@\w+(?:\(.*\))?)*' # Ignore any other annotations. 697 r'\s+(?P<prefix>(' 698 r'(private|protected|public|static|abstract|final|default|synchronized)' 699 r'\s*)*)' 700 r'(?:\s*@\w+)?' # Ignore annotations in return types. 701 r'\s*(?P<return_type>\S*?)' 702 r'\s*(?P<name>\w+)' 703 r'\s*\((?P<params>[^\)]*)\)') 704 705 706# Removes empty lines that are indented (i.e. start with 2x spaces). 707def RemoveIndentedEmptyLines(string): 708 return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE) 709 710 711def ExtractCalledByNatives(jni_params, contents, always_mangle): 712 """Parses all methods annotated with @CalledByNative. 713 714 Args: 715 jni_params: JniParams object. 716 contents: the contents of the java file. 717 always_mangle: See MangleCalledByNatives. 718 719 Returns: 720 A list of dict with information about the annotated methods. 721 TODO(bulach): return a CalledByNative object. 722 723 Raises: 724 ParseError: if unable to parse. 725 """ 726 called_by_natives = [] 727 for match in re.finditer(RE_CALLED_BY_NATIVE, contents): 728 return_type = match.group('return_type') 729 name = match.group('name') 730 if not return_type: 731 is_constructor = True 732 return_type = name 733 name = "Constructor" 734 else: 735 is_constructor = False 736 737 called_by_natives += [ 738 CalledByNative(system_class=False, 739 unchecked='Unchecked' in match.group('Unchecked'), 740 static='static' in match.group('prefix'), 741 java_class_name=match.group('annotation') or '', 742 return_type=return_type, 743 name=name, 744 is_constructor=is_constructor, 745 params=JniParams.Parse(match.group('params'))) 746 ] 747 # Check for any @CalledByNative occurrences that weren't matched. 748 unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n') 749 for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): 750 if '@CalledByNative' in line1: 751 raise ParseError('could not parse @CalledByNative method signature', 752 line1, line2) 753 return MangleCalledByNatives(jni_params, called_by_natives, always_mangle) 754 755 756def RemoveComments(contents): 757 # We need to support both inline and block comments, and we need to handle 758 # strings that contain '//' or '/*'. 759 # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java 760 # parser. Maybe we could ditch JNIFromJavaSource and just always use 761 # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. 762 # http://code.google.com/p/chromium/issues/detail?id=138941 763 def replacer(match): 764 # Replace matches that are comments with nothing; return literals/strings 765 # unchanged. 766 s = match.group(0) 767 if s.startswith('/'): 768 return '' 769 else: 770 return s 771 772 return _COMMENT_REMOVER_REGEX.sub(replacer, contents) 773 774 775class JNIFromJavaP(object): 776 """Uses 'javap' to parse a .class file and generate the JNI header file.""" 777 778 def __init__(self, contents, options): 779 self.contents = contents 780 self.namespace = options.namespace 781 for line in contents: 782 class_name = re.match( 783 '.*?(public).*?(class|interface) (?P<class_name>\S+?)( |\Z)', line) 784 if class_name: 785 self.fully_qualified_class = class_name.group('class_name') 786 break 787 self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') 788 # Java 7's javap includes type parameters in output, like HashSet<T>. Strip 789 # away the <...> and use the raw class name that Java 6 would've given us. 790 self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0] 791 self.jni_params = JniParams(self.fully_qualified_class) 792 self.java_class_name = self.fully_qualified_class.split('/')[-1] 793 if not self.namespace: 794 self.namespace = 'JNI_' + self.java_class_name 795 re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)' 796 '\((?P<params>.*?)\)') 797 self.called_by_natives = [] 798 for lineno, content in enumerate(contents[2:], 2): 799 match = re.match(re_method, content) 800 if not match: 801 continue 802 self.called_by_natives += [ 803 CalledByNative( 804 system_class=True, 805 unchecked=options.unchecked_exceptions, 806 static='static' in match.group('prefix'), 807 java_class_name='', 808 return_type=match.group('return_type').replace('.', '/'), 809 name=match.group('name'), 810 params=JniParams.Parse(match.group('params'), from_javap=True), 811 signature=JniParams.ParseJavaPSignature(contents[lineno + 1])) 812 ] 813 re_constructor = re.compile('(.*?)public ' + 814 self.fully_qualified_class.replace('/', '.') + 815 '\((?P<params>.*?)\)') 816 for lineno, content in enumerate(contents[2:], 2): 817 match = re.match(re_constructor, content) 818 if not match: 819 continue 820 self.called_by_natives += [ 821 CalledByNative(system_class=True, 822 unchecked=options.unchecked_exceptions, 823 static=False, 824 java_class_name='', 825 return_type=self.fully_qualified_class, 826 name='Constructor', 827 params=JniParams.Parse(match.group('params'), 828 from_javap=True), 829 signature=JniParams.ParseJavaPSignature( 830 contents[lineno + 1]), 831 is_constructor=True) 832 ] 833 self.called_by_natives = MangleCalledByNatives( 834 self.jni_params, self.called_by_natives, options.always_mangle) 835 self.constant_fields = [] 836 re_constant_field = re.compile('.*?public static final int (?P<name>.*?);') 837 re_constant_field_value = re.compile( 838 '.*?Constant(Value| value): int (?P<value>(-*[0-9]+)?)') 839 for lineno, content in enumerate(contents[2:], 2): 840 match = re.match(re_constant_field, content) 841 if not match: 842 continue 843 value = re.match(re_constant_field_value, contents[lineno + 2]) 844 if not value: 845 value = re.match(re_constant_field_value, contents[lineno + 3]) 846 if value: 847 self.constant_fields.append( 848 ConstantField(name=match.group('name'), value=value.group('value'))) 849 850 # We pass in an empty string for the module (which will make the JNI use the 851 # base module's files) for all javap-derived JNI. There may be a way to get 852 # the module from a jar file, but it's not needed right now. 853 self.inl_header_file_generator = InlHeaderFileGenerator( 854 '', self.namespace, self.fully_qualified_class, [], 855 self.called_by_natives, self.constant_fields, self.jni_params, options) 856 857 def GetContent(self): 858 return self.inl_header_file_generator.GetContent() 859 860 @staticmethod 861 def CreateFromClass(class_file, options): 862 class_name = os.path.splitext(os.path.basename(class_file))[0] 863 javap_path = os.path.abspath(options.javap) 864 p = subprocess.Popen( 865 args=[javap_path, '-c', '-verbose', '-s', class_name], 866 cwd=os.path.dirname(class_file), 867 stdout=subprocess.PIPE, 868 stderr=subprocess.PIPE, 869 universal_newlines=True) 870 stdout, _ = p.communicate() 871 jni_from_javap = JNIFromJavaP(stdout.split('\n'), options) 872 return jni_from_javap 873 874 875# 'Proxy' native methods are declared in an @NativeMethods interface without 876# a native qualifier and indicate that the JNI annotation processor should 877# generate code to link between the equivalent native method as if it were 878# declared statically. 879# Under the hood the annotation processor generates the actual native method 880# declaration in another class (org.chromium.base.natives.GEN_JNI) 881# but generates wrapper code so it can be called through the declaring class. 882class ProxyHelpers(object): 883 MAX_CHARS_FOR_HASHED_NATIVE_METHODS = 8 884 885 @staticmethod 886 def GetClass(short_name, name_prefix=None): 887 if not name_prefix: 888 name_prefix = '' 889 else: 890 name_prefix += '_' 891 return name_prefix + ('N' if short_name else 'GEN_JNI') 892 893 @staticmethod 894 def GetPackage(short_name, package_prefix=None): 895 package = 'J' if short_name else 'org/chromium/base/natives' 896 return GetFullyQualifiedClassWithPackagePrefix(package, package_prefix) 897 898 @staticmethod 899 def GetQualifiedClass(short_name, name_prefix=None, package_prefix=None): 900 return '%s/%s' % (ProxyHelpers.GetPackage(short_name, package_prefix), 901 ProxyHelpers.GetClass(short_name, name_prefix)) 902 903 @staticmethod 904 def CreateHashedMethodName(fully_qualified_class_name, method_name): 905 descriptor = EscapeClassName(fully_qualified_class_name + '/' + method_name) 906 907 if not isinstance(descriptor, bytes): 908 descriptor = descriptor.encode() 909 hash = hashlib.md5(descriptor).digest() 910 hash_b64 = base64.b64encode(hash, altchars=b'$_') 911 if not isinstance(hash_b64, str): 912 hash_b64 = hash_b64.decode() 913 914 long_hash = ('M' + hash_b64).rstrip('=') 915 hashed_name = long_hash[:ProxyHelpers.MAX_CHARS_FOR_HASHED_NATIVE_METHODS] 916 917 # If the method is a test-only method, we don't care about saving size on 918 # the method name, since it shouldn't show up in the binary. Additionally, 919 # if we just hash the name, our checkers which enforce that we have no 920 # "ForTesting" methods by checking for the suffix "ForTesting" will miss 921 # these. We could preserve the name entirely and not hash anything, but 922 # that risks collisions. So, instead, we just append "ForTesting" to any 923 # test-only hashes, to ensure we catch any test-only methods that 924 # shouldn't be in our final binary. 925 if _NameIsTestOnly(method_name): 926 return hashed_name + '_ForTesting' 927 return hashed_name 928 929 @staticmethod 930 def CreateProxyMethodName(fully_qualified_class, old_name, use_hash=False): 931 """Returns the literal method name for the corresponding proxy method""" 932 if use_hash: 933 return ProxyHelpers.CreateHashedMethodName(fully_qualified_class, 934 old_name) 935 936 # The annotation processor currently uses a method name 937 # org_chromium_example_foo_method_1name escaping _ to _1 938 # and then using the appending the method name to the qualified 939 # class. Since we need to escape underscores for jni to work 940 # we need to double escape _1 to _11 941 # This is the literal name of the GEN_JNI it still needs to be escaped once. 942 return EscapeClassName(fully_qualified_class + '/' + old_name) 943 944 @staticmethod 945 def ExtractStaticProxyNatives(fully_qualified_class, 946 contents, 947 ptr_type, 948 include_test_only=True): 949 methods = [] 950 first_match = True 951 module_name = None 952 for match in _NATIVE_PROXY_EXTRACTION_REGEX.finditer(contents): 953 interface_body = match.group('interface_body') 954 if first_match: 955 module_name = match.group('module_name') 956 first_match = False 957 else: 958 assert module_name == match.group( 959 'module_name' 960 ), 'JNI cannot belong to two modules in one file {} and {}'.format( 961 module_name, match.group('module_name')) 962 for method in _EXTRACT_METHODS_REGEX.finditer(interface_body): 963 name = method.group('name') 964 if not include_test_only and _NameIsTestOnly(name): 965 continue 966 967 params = JniParams.Parse(method.group('params'), use_proxy_types=True) 968 return_type = JavaTypeToProxyCast(method.group('return_type')) 969 proxy_name = ProxyHelpers.CreateProxyMethodName(fully_qualified_class, 970 name, 971 use_hash=False) 972 hashed_proxy_name = ProxyHelpers.CreateProxyMethodName( 973 fully_qualified_class, name, use_hash=True) 974 native = NativeMethod( 975 static=True, 976 java_class_name=None, 977 return_type=return_type, 978 name=name, 979 native_class_name=method.group('native_class_name'), 980 params=params, 981 is_proxy=True, 982 proxy_name=proxy_name, 983 hashed_proxy_name=hashed_proxy_name, 984 ptr_type=ptr_type) 985 methods.append(native) 986 987 if not module_name: 988 module_name = '' 989 return methods, module_name 990 991 992class JNIFromJavaSource(object): 993 """Uses the given java source file to generate the JNI header file.""" 994 995 def __init__(self, contents, fully_qualified_class, options): 996 if options.package_prefix: 997 fully_qualified_class = GetFullyQualifiedClassWithPackagePrefix( 998 fully_qualified_class, options.package_prefix) 999 contents = RemoveComments(contents) 1000 self.jni_params = JniParams(fully_qualified_class) 1001 self.jni_params.ExtractImportsAndInnerClasses(contents) 1002 jni_namespace = ExtractJNINamespace(contents) or options.namespace 1003 called_by_natives = ExtractCalledByNatives(self.jni_params, contents, 1004 options.always_mangle) 1005 1006 natives, module_name = ProxyHelpers.ExtractStaticProxyNatives( 1007 fully_qualified_class, contents, options.ptr_type) 1008 natives += ExtractNatives(contents, options.ptr_type) 1009 1010 if len(natives) == 0 and len(called_by_natives) == 0: 1011 raise SyntaxError( 1012 'Unable to find any JNI methods for %s.' % fully_qualified_class) 1013 inl_header_file_generator = InlHeaderFileGenerator( 1014 module_name, jni_namespace, fully_qualified_class, natives, 1015 called_by_natives, [], self.jni_params, options) 1016 self.content = inl_header_file_generator.GetContent() 1017 1018 def GetContent(self): 1019 return self.content 1020 1021 @staticmethod 1022 def CreateFromFile(java_file_name, options): 1023 with open(java_file_name) as f: 1024 contents = f.read() 1025 fully_qualified_class = ExtractFullyQualifiedJavaClassName( 1026 java_file_name, contents) 1027 return JNIFromJavaSource(contents, fully_qualified_class, options) 1028 1029 1030class HeaderFileGeneratorHelper(object): 1031 """Include helper methods for header generators.""" 1032 1033 def __init__(self, 1034 class_name, 1035 module_name, 1036 fully_qualified_class, 1037 use_proxy_hash, 1038 package_prefix, 1039 split_name=None, 1040 enable_jni_multiplexing=False): 1041 self.class_name = class_name 1042 self.module_name = module_name 1043 self.fully_qualified_class = fully_qualified_class 1044 self.use_proxy_hash = use_proxy_hash 1045 self.package_prefix = package_prefix 1046 self.split_name = split_name 1047 self.enable_jni_multiplexing = enable_jni_multiplexing 1048 1049 def GetStubName(self, native): 1050 """Return the name of the stub function for this native method. 1051 1052 Args: 1053 native: the native dictionary describing the method. 1054 1055 Returns: 1056 A string with the stub function name (used by the JVM). 1057 """ 1058 if native.is_proxy: 1059 if self.use_proxy_hash: 1060 method_name = EscapeClassName(native.hashed_proxy_name) 1061 else: 1062 method_name = EscapeClassName(native.proxy_name) 1063 return 'Java_%s_%s' % (EscapeClassName( 1064 ProxyHelpers.GetQualifiedClass( 1065 self.use_proxy_hash or self.enable_jni_multiplexing, 1066 self.module_name, self.package_prefix)), method_name) 1067 1068 template = Template('Java_${JAVA_NAME}_native${NAME}') 1069 1070 java_name = self.fully_qualified_class 1071 if native.java_class_name: 1072 java_name += '$' + native.java_class_name 1073 1074 values = {'NAME': native.name, 'JAVA_NAME': EscapeClassName(java_name)} 1075 return template.substitute(values) 1076 1077 def GetUniqueClasses(self, origin): 1078 ret = collections.OrderedDict() 1079 for entry in origin: 1080 if isinstance(entry, NativeMethod) and entry.is_proxy: 1081 short_name = self.use_proxy_hash or self.enable_jni_multiplexing 1082 ret[ProxyHelpers.GetClass(short_name, self.module_name)] \ 1083 = ProxyHelpers.GetQualifiedClass(short_name, self.module_name, self.package_prefix) 1084 continue 1085 ret[self.class_name] = self.fully_qualified_class 1086 1087 class_name = self.class_name 1088 jni_class_path = self.fully_qualified_class 1089 if entry.java_class_name: 1090 class_name = entry.java_class_name 1091 jni_class_path = self.fully_qualified_class + '$' + class_name 1092 ret[class_name] = jni_class_path 1093 return ret 1094 1095 def GetClassPathLines(self, classes, declare_only=False): 1096 """Returns the ClassPath constants.""" 1097 ret = [] 1098 if declare_only: 1099 template = Template(""" 1100extern const char kClassPath_${JAVA_CLASS}[]; 1101""") 1102 else: 1103 template = Template(""" 1104JNI_REGISTRATION_EXPORT extern const char kClassPath_${JAVA_CLASS}[]; 1105const char kClassPath_${JAVA_CLASS}[] = \ 1106"${JNI_CLASS_PATH}"; 1107""") 1108 1109 for full_clazz in classes.values(): 1110 values = { 1111 'JAVA_CLASS': EscapeClassName(full_clazz), 1112 'JNI_CLASS_PATH': full_clazz, 1113 } 1114 # Since all proxy methods use the same class, defining this in every 1115 # header file would result in duplicated extern initializations. 1116 if full_clazz != ProxyHelpers.GetQualifiedClass( 1117 self.use_proxy_hash or self.enable_jni_multiplexing, self.module_name, 1118 self.package_prefix): 1119 ret += [template.substitute(values)] 1120 1121 class_getter = """\ 1122#ifndef ${JAVA_CLASS}_clazz_defined 1123#define ${JAVA_CLASS}_clazz_defined 1124inline jclass ${JAVA_CLASS}_clazz(JNIEnv* env) { 1125 return base::android::LazyGetClass(env, kClassPath_${JAVA_CLASS}, \ 1126${MAYBE_SPLIT_NAME_ARG}&g_${JAVA_CLASS}_clazz); 1127} 1128#endif 1129""" 1130 if declare_only: 1131 template = Template("""\ 1132extern std::atomic<jclass> g_${JAVA_CLASS}_clazz; 1133""" + class_getter) 1134 else: 1135 template = Template("""\ 1136// Leaking this jclass as we cannot use LazyInstance from some threads. 1137JNI_REGISTRATION_EXPORT std::atomic<jclass> g_${JAVA_CLASS}_clazz(nullptr); 1138""" + class_getter) 1139 1140 for full_clazz in classes.values(): 1141 values = { 1142 'JAVA_CLASS': 1143 EscapeClassName(full_clazz), 1144 'MAYBE_SPLIT_NAME_ARG': 1145 (('"%s", ' % self.split_name) if self.split_name else '') 1146 } 1147 # Since all proxy methods use the same class, defining this in every 1148 # header file would result in duplicated extern initializations. 1149 if full_clazz != ProxyHelpers.GetQualifiedClass( 1150 self.use_proxy_hash or self.enable_jni_multiplexing, self.module_name, 1151 self.package_prefix): 1152 ret += [template.substitute(values)] 1153 1154 return ''.join(ret) 1155 1156 1157class InlHeaderFileGenerator(object): 1158 """Generates an inline header file for JNI integration.""" 1159 1160 def __init__(self, module_name, namespace, fully_qualified_class, natives, 1161 called_by_natives, constant_fields, jni_params, options): 1162 self.namespace = namespace 1163 self.fully_qualified_class = fully_qualified_class 1164 self.class_name = self.fully_qualified_class.split('/')[-1] 1165 self.natives = natives 1166 self.called_by_natives = called_by_natives 1167 self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' 1168 self.constant_fields = constant_fields 1169 self.jni_params = jni_params 1170 self.options = options 1171 self.helper = HeaderFileGeneratorHelper( 1172 self.class_name, 1173 module_name, 1174 fully_qualified_class, 1175 self.options.use_proxy_hash, 1176 self.options.package_prefix, 1177 split_name=self.options.split_name, 1178 enable_jni_multiplexing=self.options.enable_jni_multiplexing) 1179 1180 def GetContent(self): 1181 """Returns the content of the JNI binding file.""" 1182 template = Template("""\ 1183// Copyright 2014 The Chromium Authors 1184// Use of this source code is governed by a BSD-style license that can be 1185// found in the LICENSE file. 1186 1187 1188// This file is autogenerated by 1189// ${SCRIPT_NAME} 1190// For 1191// ${FULLY_QUALIFIED_CLASS} 1192 1193#ifndef ${HEADER_GUARD} 1194#define ${HEADER_GUARD} 1195 1196#include <jni.h> 1197 1198${INCLUDES} 1199 1200// Step 1: Forward declarations. 1201$CLASS_PATH_DEFINITIONS 1202 1203// Step 2: Constants (optional). 1204 1205$CONSTANT_FIELDS\ 1206 1207// Step 3: Method stubs. 1208$METHOD_STUBS 1209 1210#endif // ${HEADER_GUARD} 1211""") 1212 values = { 1213 'SCRIPT_NAME': self.options.script_name, 1214 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, 1215 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), 1216 'CONSTANT_FIELDS': self.GetConstantFieldsString(), 1217 'METHOD_STUBS': self.GetMethodStubsString(), 1218 'HEADER_GUARD': self.header_guard, 1219 'INCLUDES': self.GetIncludesString(), 1220 } 1221 open_namespace = self.GetOpenNamespaceString() 1222 if open_namespace: 1223 close_namespace = self.GetCloseNamespaceString() 1224 values['METHOD_STUBS'] = '\n'.join( 1225 [open_namespace, values['METHOD_STUBS'], close_namespace]) 1226 1227 constant_fields = values['CONSTANT_FIELDS'] 1228 if constant_fields: 1229 values['CONSTANT_FIELDS'] = '\n'.join( 1230 [open_namespace, constant_fields, close_namespace]) 1231 1232 return WrapOutput(template.substitute(values)) 1233 1234 def GetClassPathDefinitionsString(self): 1235 classes = self.helper.GetUniqueClasses(self.called_by_natives) 1236 classes.update(self.helper.GetUniqueClasses(self.natives)) 1237 return self.helper.GetClassPathLines(classes) 1238 1239 def GetConstantFieldsString(self): 1240 if not self.constant_fields: 1241 return '' 1242 ret = ['enum Java_%s_constant_fields {' % self.class_name] 1243 for c in self.constant_fields: 1244 ret += [' %s = %s,' % (c.name, c.value)] 1245 ret += ['};', ''] 1246 return '\n'.join(ret) 1247 1248 def GetMethodStubsString(self): 1249 """Returns the code corresponding to method stubs.""" 1250 ret = [] 1251 for native in self.natives: 1252 ret += [self.GetNativeStub(native)] 1253 ret += self.GetLazyCalledByNativeMethodStubs() 1254 return '\n'.join(ret) 1255 1256 def GetLazyCalledByNativeMethodStubs(self): 1257 return [ 1258 self.GetLazyCalledByNativeMethodStub(called_by_native) 1259 for called_by_native in self.called_by_natives 1260 ] 1261 1262 def GetIncludesString(self): 1263 if not self.options.includes: 1264 return '' 1265 includes = self.options.includes.split(',') 1266 return '\n'.join('#include "%s"' % x for x in includes) + '\n' 1267 1268 def GetOpenNamespaceString(self): 1269 if self.namespace: 1270 all_namespaces = [ 1271 'namespace %s {' % ns for ns in self.namespace.split('::') 1272 ] 1273 return '\n'.join(all_namespaces) + '\n' 1274 return '' 1275 1276 def GetCloseNamespaceString(self): 1277 if self.namespace: 1278 all_namespaces = [ 1279 '} // namespace %s' % ns for ns in self.namespace.split('::') 1280 ] 1281 all_namespaces.reverse() 1282 return '\n' + '\n'.join(all_namespaces) 1283 return '' 1284 1285 def GetCalledByNativeParamsInDeclaration(self, called_by_native): 1286 return ',\n '.join([ 1287 JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' + param.name 1288 for param in called_by_native.params 1289 ]) 1290 1291 def GetJavaParamRefForCall(self, c_type, name): 1292 return Template( 1293 'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({ 1294 'TYPE': 1295 c_type, 1296 'NAME': 1297 name, 1298 }) 1299 1300 def GetImplementationMethodName(self, native): 1301 class_name = self.class_name 1302 if native.java_class_name is not None: 1303 # Inner class 1304 class_name = native.java_class_name 1305 1306 return 'JNI_%s_%s' % (class_name, native.name) 1307 1308 def GetNativeStub(self, native): 1309 is_method = native.type == 'method' 1310 1311 if is_method: 1312 params = native.params[1:] 1313 else: 1314 params = native.params 1315 1316 params_in_call = ['env'] 1317 if not native.static: 1318 # Add jcaller param. 1319 params_in_call.append(self.GetJavaParamRefForCall('jobject', 'jcaller')) 1320 1321 for p in params: 1322 c_type = JavaDataTypeToC(p.datatype) 1323 if re.match(RE_SCOPED_JNI_TYPES, c_type): 1324 params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name)) 1325 else: 1326 params_in_call.append(p.name) 1327 1328 params_in_declaration = _GetParamsInDeclaration(native) 1329 params_in_call = ', '.join(params_in_call) 1330 1331 return_type = return_declaration = JavaDataTypeToC(native.return_type) 1332 post_call = '' 1333 if re.match(RE_SCOPED_JNI_TYPES, return_type): 1334 post_call = '.Release()' 1335 return_declaration = ( 1336 'base::android::ScopedJavaLocalRef<' + return_type + '>') 1337 profiling_entered_native = '' 1338 if self.options.enable_profiling: 1339 profiling_entered_native = ' JNI_LINK_SAVED_FRAME_POINTER;\n' 1340 1341 values = { 1342 'RETURN': return_type, 1343 'RETURN_DECLARATION': return_declaration, 1344 'NAME': native.name, 1345 'IMPL_METHOD_NAME': self.GetImplementationMethodName(native), 1346 'PARAMS': ',\n '.join(params_in_declaration), 1347 'PARAMS_IN_STUB': GetParamsInStub(native), 1348 'PARAMS_IN_CALL': params_in_call, 1349 'POST_CALL': post_call, 1350 'STUB_NAME': self.helper.GetStubName(native), 1351 'PROFILING_ENTERED_NATIVE': profiling_entered_native, 1352 } 1353 1354 namespace_qual = self.namespace + '::' if self.namespace else '' 1355 if is_method: 1356 optional_error_return = JavaReturnValueToC(native.return_type) 1357 if optional_error_return: 1358 optional_error_return = ', ' + optional_error_return 1359 values.update({ 1360 'OPTIONAL_ERROR_RETURN': optional_error_return, 1361 'PARAM0_NAME': native.params[0].name, 1362 'P0_TYPE': native.p0_type, 1363 }) 1364 template = Template("""\ 1365JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}( 1366 JNIEnv* env, 1367 ${PARAMS_IN_STUB}) { 1368${PROFILING_ENTERED_NATIVE}\ 1369 ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); 1370 CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN}); 1371 return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL}; 1372} 1373""") 1374 else: 1375 if values['PARAMS']: 1376 values['PARAMS'] = ', ' + values['PARAMS'] 1377 template = Template("""\ 1378static ${RETURN_DECLARATION} ${IMPL_METHOD_NAME}(JNIEnv* env${PARAMS}); 1379 1380JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}( 1381 JNIEnv* env, 1382 ${PARAMS_IN_STUB}) { 1383${PROFILING_ENTERED_NATIVE}\ 1384 return ${IMPL_METHOD_NAME}(${PARAMS_IN_CALL})${POST_CALL}; 1385} 1386""") 1387 1388 return RemoveIndentedEmptyLines(template.substitute(values)) 1389 1390 def GetArgument(self, param): 1391 if param.datatype == 'int': 1392 return 'as_jint(' + param.name + ')' 1393 elif re.match(RE_SCOPED_JNI_TYPES, JavaDataTypeToC(param.datatype)): 1394 return param.name + '.obj()' 1395 else: 1396 return param.name 1397 1398 def GetArgumentsInCall(self, params): 1399 """Return a string of arguments to call from native into Java""" 1400 return [self.GetArgument(p) for p in params] 1401 1402 def GetCalledByNativeValues(self, called_by_native): 1403 """Fills in necessary values for the CalledByNative methods.""" 1404 java_class_only = called_by_native.java_class_name or self.class_name 1405 java_class = self.fully_qualified_class 1406 if called_by_native.java_class_name: 1407 java_class += '$' + called_by_native.java_class_name 1408 1409 if called_by_native.static or called_by_native.is_constructor: 1410 first_param_in_declaration = '' 1411 first_param_in_call = 'clazz' 1412 else: 1413 first_param_in_declaration = ( 1414 ', const base::android::JavaRef<jobject>& obj') 1415 first_param_in_call = 'obj.obj()' 1416 params_in_declaration = self.GetCalledByNativeParamsInDeclaration( 1417 called_by_native) 1418 if params_in_declaration: 1419 params_in_declaration = ', ' + params_in_declaration 1420 params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params)) 1421 if params_in_call: 1422 params_in_call = ', ' + params_in_call 1423 pre_call = '' 1424 post_call = '' 1425 if called_by_native.static_cast: 1426 pre_call = 'static_cast<%s>(' % called_by_native.static_cast 1427 post_call = ')' 1428 check_exception = 'Unchecked' 1429 method_id_member_name = 'call_context.method_id' 1430 if not called_by_native.unchecked: 1431 check_exception = 'Checked' 1432 method_id_member_name = 'call_context.base.method_id' 1433 return_type = JavaDataTypeToC(called_by_native.return_type) 1434 optional_error_return = JavaReturnValueToC(called_by_native.return_type) 1435 if optional_error_return: 1436 optional_error_return = ', ' + optional_error_return 1437 return_declaration = '' 1438 return_clause = '' 1439 if return_type != 'void': 1440 pre_call = ' ' + pre_call 1441 return_declaration = return_type + ' ret =' 1442 if re.match(RE_SCOPED_JNI_TYPES, return_type): 1443 return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>' 1444 return_clause = 'return ' + return_type + '(env, ret);' 1445 else: 1446 return_clause = 'return ret;' 1447 profiling_leaving_native = '' 1448 if self.options.enable_profiling: 1449 profiling_leaving_native = ' JNI_SAVE_FRAME_POINTER;\n' 1450 jni_name = called_by_native.name 1451 jni_return_type = called_by_native.return_type 1452 if called_by_native.is_constructor: 1453 jni_name = '<init>' 1454 jni_return_type = 'void' 1455 if called_by_native.signature: 1456 jni_signature = called_by_native.signature 1457 else: 1458 jni_signature = self.jni_params.Signature(called_by_native.params, 1459 jni_return_type) 1460 java_name_full = java_class.replace('/', '.') + '.' + jni_name 1461 return { 1462 'JAVA_CLASS_ONLY': java_class_only, 1463 'JAVA_CLASS': EscapeClassName(java_class), 1464 'RETURN_TYPE': return_type, 1465 'OPTIONAL_ERROR_RETURN': optional_error_return, 1466 'RETURN_DECLARATION': return_declaration, 1467 'RETURN_CLAUSE': return_clause, 1468 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, 1469 'PARAMS_IN_DECLARATION': params_in_declaration, 1470 'PRE_CALL': pre_call, 1471 'POST_CALL': post_call, 1472 'ENV_CALL': called_by_native.env_call, 1473 'FIRST_PARAM_IN_CALL': first_param_in_call, 1474 'PARAMS_IN_CALL': params_in_call, 1475 'CHECK_EXCEPTION': check_exception, 1476 'PROFILING_LEAVING_NATIVE': profiling_leaving_native, 1477 'JNI_NAME': jni_name, 1478 'JNI_SIGNATURE': jni_signature, 1479 'METHOD_ID_MEMBER_NAME': method_id_member_name, 1480 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, 1481 'METHOD_ID_TYPE': 'STATIC' if called_by_native.static else 'INSTANCE', 1482 'JAVA_NAME_FULL': java_name_full, 1483 } 1484 1485 def GetLazyCalledByNativeMethodStub(self, called_by_native): 1486 """Returns a string.""" 1487 function_signature_template = Template("""\ 1488static ${RETURN_TYPE} Java_${JAVA_CLASS_ONLY}_${METHOD_ID_VAR_NAME}(\ 1489JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""") 1490 function_header_template = Template("""\ 1491${FUNCTION_SIGNATURE} {""") 1492 function_header_with_unused_template = Template("""\ 1493[[maybe_unused]] ${FUNCTION_SIGNATURE}; 1494${FUNCTION_SIGNATURE} {""") 1495 template = Template(""" 1496static std::atomic<jmethodID> g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(nullptr); 1497${FUNCTION_HEADER} 1498 jclass clazz = ${JAVA_CLASS}_clazz(env); 1499 CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL}, 1500 ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN}); 1501 1502 jni_generator::JniJavaCallContext${CHECK_EXCEPTION} call_context; 1503 call_context.Init< 1504 base::android::MethodID::TYPE_${METHOD_ID_TYPE}>( 1505 env, 1506 clazz, 1507 "${JNI_NAME}", 1508 ${JNI_SIGNATURE}, 1509 &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); 1510 1511${PROFILING_LEAVING_NATIVE}\ 1512 ${RETURN_DECLARATION} 1513 ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, 1514 ${METHOD_ID_MEMBER_NAME}${PARAMS_IN_CALL})${POST_CALL}; 1515 ${RETURN_CLAUSE} 1516}""") 1517 values = self.GetCalledByNativeValues(called_by_native) 1518 values['FUNCTION_SIGNATURE'] = ( 1519 function_signature_template.substitute(values)) 1520 if called_by_native.system_class: 1521 values['FUNCTION_HEADER'] = ( 1522 function_header_with_unused_template.substitute(values)) 1523 else: 1524 values['FUNCTION_HEADER'] = function_header_template.substitute(values) 1525 return RemoveIndentedEmptyLines(template.substitute(values)) 1526 1527 def GetTraceEventForNameTemplate(self, name_template, values): 1528 name = Template(name_template).substitute(values) 1529 return ' TRACE_EVENT0("jni", "%s");\n' % name 1530 1531 1532def WrapOutput(output): 1533 ret = [] 1534 for line in output.splitlines(): 1535 # Do not wrap preprocessor directives or comments. 1536 if len(line) < _WRAP_LINE_LENGTH or line[0] == '#' or line.startswith('//'): 1537 ret.append(line) 1538 else: 1539 # Assumes that the line is not already indented as a continuation line, 1540 # which is not always true (oh well). 1541 first_line_indent = (len(line) - len(line.lstrip())) 1542 wrapper = _WRAPPERS_BY_INDENT[first_line_indent] 1543 ret.extend(wrapper.wrap(line)) 1544 ret += [''] 1545 return '\n'.join(ret) 1546 1547 1548def GenerateJNIHeader(input_file, output_file, options): 1549 try: 1550 if os.path.splitext(input_file)[1] == '.class': 1551 # The current package-prefix implementation does not support adding 1552 # prefix to java compiled classes. The current support is only for 1553 # java source files. 1554 # TODO: uncomment assertion. This currently breaks because of Runnable.class. 1555 # assert not options.package_prefix 1556 jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options) 1557 content = jni_from_javap.GetContent() 1558 else: 1559 jni_from_java_source = JNIFromJavaSource.CreateFromFile( 1560 input_file, options) 1561 content = jni_from_java_source.GetContent() 1562 except ParseError as e: 1563 print(e) 1564 sys.exit(1) 1565 if output_file: 1566 with action_helpers.atomic_output(output_file, mode='w') as f: 1567 f.write(content) 1568 else: 1569 print(content) 1570 1571 1572def GetScriptName(): 1573 script_components = os.path.abspath(__file__).split(os.path.sep) 1574 base_index = 0 1575 for idx, value in enumerate(script_components): 1576 if value == 'base' or value == 'third_party': 1577 base_index = idx 1578 break 1579 return os.sep.join(script_components[base_index:]) 1580 1581 1582def _RemoveStaleHeaders(path, output_names): 1583 if not os.path.isdir(path): 1584 return 1585 # Do not remove output files so that timestamps on declared outputs are not 1586 # modified unless their contents are changed (avoids reverse deps needing to 1587 # be rebuilt). 1588 preserve = set(output_names) 1589 for root, _, files in os.walk(path): 1590 for f in files: 1591 if f not in preserve: 1592 file_path = os.path.join(root, f) 1593 if os.path.isfile(file_path) and file_path.endswith('.h'): 1594 os.remove(file_path) 1595 1596 1597def main(): 1598 description = """ 1599This script will parse the given java source code extracting the native 1600declarations and print the header file to stdout (or a file). 1601See SampleForTests.java for more details. 1602 """ 1603 parser = argparse.ArgumentParser(description=description) 1604 1605 parser.add_argument( 1606 '-j', 1607 '--jar_file', 1608 dest='jar_file', 1609 help='Extract the list of input files from' 1610 ' a specified jar file.' 1611 ' Uses javap to extract the methods from a' 1612 ' pre-compiled class. --input should point' 1613 ' to pre-compiled Java .class files.') 1614 parser.add_argument( 1615 '-n', 1616 dest='namespace', 1617 help='Uses as a namespace in the generated header ' 1618 'instead of the javap class name, or when there is ' 1619 'no JNINamespace annotation in the java source.') 1620 parser.add_argument('--input_file', 1621 action='append', 1622 required=True, 1623 dest='input_files', 1624 help='Input filenames, or paths within a .jar if ' 1625 '--jar-file is used.') 1626 parser.add_argument('--output_dir', required=True, help='Output directory.') 1627 # TODO(agrieve): --prev_output_dir used only to make incremental builds work. 1628 # Remove --prev_output_dir at some point after 2022. 1629 parser.add_argument('--prev_output_dir', 1630 help='Delete headers found in this directory.') 1631 parser.add_argument('--output_name', 1632 action='append', 1633 dest='output_names', 1634 help='Output filenames within output directory.') 1635 parser.add_argument( 1636 '--script_name', 1637 default=GetScriptName(), 1638 help='The name of this script in the generated ' 1639 'header.') 1640 parser.add_argument( 1641 '--includes', 1642 help='The comma-separated list of header files to ' 1643 'include in the generated header.') 1644 parser.add_argument( 1645 '--ptr_type', 1646 default='int', 1647 choices=['int', 'long'], 1648 help='The type used to represent native pointers in ' 1649 'Java code. For 32-bit, use int; ' 1650 'for 64-bit, use long.') 1651 parser.add_argument('--cpp', default='cpp', help='The path to cpp command.') 1652 parser.add_argument( 1653 '--javap', 1654 default=build_utils.JAVAP_PATH, 1655 help='The path to javap command.') 1656 parser.add_argument( 1657 '--enable_profiling', 1658 action='store_true', 1659 help='Add additional profiling instrumentation.') 1660 parser.add_argument( 1661 '--always_mangle', action='store_true', help='Mangle all function names') 1662 parser.add_argument('--unchecked_exceptions', 1663 action='store_true', 1664 help='Do not check that no exceptions were thrown.') 1665 parser.add_argument('--include_test_only', 1666 action='store_true', 1667 help='Whether to maintain ForTesting JNI methods.') 1668 parser.add_argument( 1669 '--use_proxy_hash', 1670 action='store_true', 1671 help='Hashes the native declaration of methods used ' 1672 'in @JniNatives interface.') 1673 parser.add_argument('--enable_jni_multiplexing', 1674 action='store_true', 1675 help='Enables JNI multiplexing for Java native methods') 1676 parser.add_argument( 1677 '--split_name', 1678 help='Split name that the Java classes should be loaded from.') 1679 parser.add_argument( 1680 '--package_prefix', 1681 help= 1682 'Adds a prefix to the classes fully qualified-name. Effectively changing a class name from' 1683 'foo.bar -> prefix.foo.bar') 1684 # TODO(agrieve): --stamp used only to make incremental builds work. 1685 # Remove --stamp at some point after 2022. 1686 parser.add_argument('--stamp', 1687 help='Process --prev_output_dir and touch this file.') 1688 args = parser.parse_args() 1689 # Kotlin files are not supported by jni_generator.py, but they do end up in 1690 # the list of source files passed to jni_generator.py. 1691 input_files = [f for f in args.input_files if not f.endswith('.kt')] 1692 output_names = args.output_names 1693 1694 if args.prev_output_dir: 1695 _RemoveStaleHeaders(args.prev_output_dir, []) 1696 1697 if args.stamp: 1698 build_utils.Touch(args.stamp) 1699 sys.exit(0) 1700 1701 if output_names: 1702 # Remove existing headers so that moving .java source files but not updating 1703 # the corresponding C++ include will be a compile failure (otherwise 1704 # incremental builds will usually not catch this). 1705 _RemoveStaleHeaders(args.output_dir, output_names) 1706 else: 1707 output_names = [None] * len(input_files) 1708 temp_dir = tempfile.mkdtemp() 1709 try: 1710 if args.jar_file: 1711 with zipfile.ZipFile(args.jar_file) as z: 1712 z.extractall(temp_dir, input_files) 1713 input_files = [os.path.join(temp_dir, f) for f in input_files] 1714 1715 for java_path, header_name in zip(input_files, output_names): 1716 if header_name: 1717 header_path = os.path.join(args.output_dir, header_name) 1718 else: 1719 header_path = None 1720 GenerateJNIHeader(java_path, header_path, args) 1721 finally: 1722 shutil.rmtree(temp_dir) 1723 1724 1725if __name__ == '__main__': 1726 sys.exit(main()) 1727