• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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