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