• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 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 "link" command."""
5
6import collections
7import functools
8import hashlib
9import multiprocessing
10import os
11import pathlib
12import posixpath
13import re
14import string
15import sys
16import zipfile
17
18import common
19import java_types
20import jni_generator
21import parse
22import proxy
23
24
25# All but FULL_CLASS_NAME, which is used only for sorting.
26MERGEABLE_KEYS = [
27    'CLASS_PATH_DECLARATIONS',
28    'FORWARD_DECLARATIONS',
29    'JNI_NATIVE_METHOD',
30    'JNI_NATIVE_METHOD_ARRAY',
31    'PROXY_NATIVE_SIGNATURES',
32    'FORWARDING_PROXY_METHODS',
33    'PROXY_NATIVE_METHOD_ARRAY',
34    'REGISTER_NATIVES',
35]
36
37
38def _Generate(options, native_sources, java_sources):
39  """Generates files required to perform JNI registration.
40
41  Generates a srcjar containing a single class, GEN_JNI, that contains all
42  native method declarations.
43
44  Optionally generates a header file that provides RegisterNatives to perform
45  JNI registration.
46
47  Args:
48    options: arguments from the command line
49    native_sources: A list of java file paths. The source of truth.
50    java_sources: A list of java file paths. Used to assert against
51      native_sources.
52  """
53  # Without multiprocessing, script takes ~13 seconds for chrome_public_apk
54  # on a z620. With multiprocessing, takes ~2 seconds.
55  results = []
56  cached_results_for_stubs = {}
57  with multiprocessing.Pool() as pool:
58    # The native-based sources are the "source of truth" - the Java based ones
59    # will be used later to generate stubs and make assertions.
60    for result in pool.imap_unordered(functools.partial(_DictForPath, options),
61                                      native_sources):
62      d, path, jni_obj = result
63      if d:
64        results.append(d)
65      cached_results_for_stubs[path] = jni_obj
66
67  native_sources_set = {d['FILE_PATH'] for d in results}
68  java_sources_set = set(java_sources)
69  stub_dicts = _GenerateStubsAndAssert(options, native_sources_set,
70                                       java_sources_set,
71                                       cached_results_for_stubs)
72  # Sort to make output deterministic.
73  results.sort(key=lambda d: d['FULL_CLASS_NAME'])
74
75  combined_dict = {}
76  for key in MERGEABLE_KEYS:
77    combined_dict[key] = ''.join(d.get(key, '') for d in results)
78
79  short_gen_jni_class = proxy.get_gen_jni_class(
80      short=True,
81      name_prefix=options.module_name,
82      package_prefix=options.package_prefix)
83  full_gen_jni_class = proxy.get_gen_jni_class(
84      short=False,
85      name_prefix=options.module_name,
86      package_prefix=options.package_prefix)
87  # PROXY_NATIVE_SIGNATURES and PROXY_NATIVE_METHOD_ARRAY will have
88  # duplicates for JNI multiplexing since all native methods with similar
89  # signatures map to the same proxy. Similarly, there may be multiple switch
90  # case entries for the same proxy signatures.
91  if options.enable_jni_multiplexing:
92    proxy_signatures_list = sorted(
93        set(combined_dict['PROXY_NATIVE_SIGNATURES'].split('\n')))
94    combined_dict['PROXY_NATIVE_SIGNATURES'] = '\n'.join(
95        signature for signature in proxy_signatures_list)
96
97    proxy_native_array_list = sorted(
98        set(combined_dict['PROXY_NATIVE_METHOD_ARRAY'].split('},\n')))
99    combined_dict['PROXY_NATIVE_METHOD_ARRAY'] = '},\n'.join(
100        p for p in proxy_native_array_list if p != '') + '}'
101    signature_to_cases = collections.defaultdict(list)
102    for d in results:
103      for signature, cases in d['SIGNATURE_TO_CASES'].items():
104        signature_to_cases[signature].extend(cases)
105    combined_dict['FORWARDING_CALLS'] = _AddForwardingCalls(
106        signature_to_cases, short_gen_jni_class)
107
108  if options.header_path:
109    header_guard = os.path.splitext(options.header_path)[0].upper() + '_'
110    header_guard = re.sub(r'[/.-]', '_', header_guard)
111    combined_dict['HEADER_GUARD'] = header_guard
112    combined_dict['NAMESPACE'] = options.namespace or ''
113    header_content = CreateFromDict(options, combined_dict)
114    with common.atomic_output(options.header_path, mode='w') as f:
115      f.write(header_content)
116
117  stub_methods_string = ''.join(d['STUBS'] for d in stub_dicts)
118
119  with common.atomic_output(options.srcjar_path) as f:
120    with zipfile.ZipFile(f, 'w') as srcjar:
121      if options.use_proxy_hash or options.enable_jni_multiplexing:
122        gen_jni_class = short_gen_jni_class
123      else:
124        gen_jni_class = full_gen_jni_class
125
126      if options.use_proxy_hash or options.enable_jni_multiplexing:
127        # J/N.java
128        common.add_to_zip_hermetic(
129            srcjar,
130            f'{short_gen_jni_class.full_name_with_slashes}.java',
131            data=CreateProxyJavaFromDict(options, gen_jni_class, combined_dict))
132        # org/jni_zero/GEN_JNI.java
133        common.add_to_zip_hermetic(
134            srcjar,
135            f'{full_gen_jni_class.full_name_with_slashes}.java',
136            data=CreateProxyJavaFromDict(options,
137                                         full_gen_jni_class,
138                                         combined_dict,
139                                         stub_methods=stub_methods_string,
140                                         forwarding=True))
141      else:
142        # org/jni_zero/GEN_JNI.java
143        common.add_to_zip_hermetic(
144            srcjar,
145            f'{full_gen_jni_class.full_name_with_slashes}.java',
146            data=CreateProxyJavaFromDict(options,
147                                         gen_jni_class,
148                                         combined_dict,
149                                         stub_methods=stub_methods_string))
150
151
152def _GenerateStubsAndAssert(options, native_sources_set, java_sources_set,
153                            cached_results_for_stubs):
154  native_only = native_sources_set - java_sources_set
155  java_only = java_sources_set - native_sources_set
156  # Using stub_only because we just need this to do a boolean check to see if
157  # the files have JNI - we don't need any actual output.
158  dict_by_path = {
159      f: _DictForPath(options,
160                      f,
161                      stub_only=True,
162                      cached_results_for_stubs=cached_results_for_stubs)[0]
163      for f in java_only
164  }
165  dict_by_path = {k: v for k, v in sorted(dict_by_path.items()) if v}
166  failed = False
167  if not options.add_stubs_for_missing_native and dict_by_path:
168    failed = True
169    warning_message = '''Failed JNI assertion!
170We reference Java files which use JNI, but our native library does not depend on
171the corresponding generate_jni().
172To bypass this check, you can add stubs to Java with add_stubs_for_missing_jni.
173Excess Java files below:
174'''
175    sys.stderr.write(warning_message)
176    sys.stderr.write(', '.join(dict_by_path))
177    sys.stderr.write('\n')
178  if not options.remove_uncalled_methods and native_only:
179    failed = True
180    warning_message = '''Failed JNI assertion!
181Our native library depends on generate_jnis which reference Java files that we
182do not include in our final dex.
183To bypass this check, delete these extra JNI methods with remove_uncalled_jni.
184Unneeded Java files below:
185'''
186    sys.stderr.write(warning_message)
187    sys.stderr.write(str(native_only))
188    sys.stderr.write('\n')
189  if failed:
190    sys.exit(1)
191  return list(dict_by_path.values())
192
193
194def _DictForPath(options, path, stub_only=False, cached_results_for_stubs=None):
195  # The cached results are generated by the real runs, which happen first, so
196  # the cache is only for the stub checks after.
197  assert (cached_results_for_stubs is not None) == stub_only
198  jni_obj = stub_only and cached_results_for_stubs.get(path)
199  if not jni_obj:
200    parsed_file = parse.parse_java_file(path,
201                                        package_prefix=options.package_prefix)
202    jni_obj = jni_generator.JNIFromJavaSource(parsed_file, options)
203    if not options.include_test_only:
204      jni_obj.RemoveTestOnlyNatives()
205
206  if jni_obj.module_name != options.module_name:
207    # Ignoring any code from modules we aren't looking at.
208    return None, path, jni_obj
209
210  if not jni_obj.natives:
211    return None, path, jni_obj
212  if stub_only:
213    return {'STUBS': _GenerateStubs(jni_obj.proxy_natives)}, path, jni_obj
214
215  # The namespace for the content is separate from the namespace for the
216  # generated header file.
217  dict_generator = DictionaryGenerator(jni_obj, options)
218  return dict_generator.Generate(), path, jni_obj
219
220
221def _GenerateStubs(natives):
222  final_string = ''
223  for native in natives:
224    template = string.Template("""
225
226    public static ${RETURN_TYPE} ${METHOD_NAME}(${PARAMS_WITH_TYPES}) {
227        throw new RuntimeException("Stub - not implemented!");
228    }""")
229
230    final_string += template.substitute({
231        'RETURN_TYPE':
232        native.proxy_return_type.to_java(),
233        'METHOD_NAME':
234        native.proxy_name,
235        'PARAMS_WITH_TYPES':
236        native.proxy_params.to_java_declaration(),
237    })
238  return final_string
239
240
241def _AddForwardingCalls(signature_to_cases, short_gen_jni_class):
242  template = string.Template("""
243JNI_BOUNDARY_EXPORT ${RETURN} Java_${CLASS_NAME}_${PROXY_SIGNATURE}(
244    JNIEnv* env,
245    jclass jcaller,
246    ${PARAMS_IN_STUB}) {
247        switch (switch_num) {
248          ${CASES}
249          default:
250            JNI_ZERO_ELOG("${CLASS_NAME}_${PROXY_SIGNATURE} was called with an \
251invalid switch number: %d", switch_num);
252            JNI_ZERO_DCHECK(false);
253            return${DEFAULT_RETURN};
254        }
255}""")
256
257  switch_statements = []
258  for signature, cases in sorted(signature_to_cases.items()):
259    params_in_stub = _GetJavaToNativeParamsList(signature.param_types)
260    switch_statements.append(
261        template.substitute({
262            'RETURN':
263            signature.return_type.to_cpp(),
264            'CLASS_NAME':
265            common.escape_class_name(
266                short_gen_jni_class.full_name_with_slashes),
267            'PROXY_SIGNATURE':
268            common.escape_class_name(_GetMultiplexProxyName(signature)),
269            'PARAMS_IN_STUB':
270            params_in_stub,
271            'CASES':
272            ''.join(cases),
273            'DEFAULT_RETURN':
274            '' if signature.return_type.is_void() else ' {}',
275        }))
276
277  return ''.join(s for s in switch_statements)
278
279
280def _SetProxyRegistrationFields(options, gen_jni_class, registration_dict):
281  registration_template = string.Template("""\
282
283static const JNINativeMethod kMethods_${ESCAPED_PROXY_CLASS}[] = {
284${KMETHODS}
285};
286
287namespace {
288
289JNI_ZERO_COMPONENT_BUILD_EXPORT bool ${REGISTRATION_NAME}(JNIEnv* env) {
290  const int number_of_methods = std::size(kMethods_${ESCAPED_PROXY_CLASS});
291
292  base::android::ScopedJavaLocalRef<jclass> native_clazz =
293      base::android::GetClass(env, "${PROXY_CLASS}");
294  if (env->RegisterNatives(
295      native_clazz.obj(),
296      kMethods_${ESCAPED_PROXY_CLASS},
297      number_of_methods) < 0) {
298
299    jni_generator::HandleRegistrationError(env, native_clazz.obj(), __FILE__);
300    return false;
301  }
302
303  return true;
304}
305
306}  // namespace
307""")
308
309  registration_call = string.Template("""\
310
311  // Register natives in a proxy.
312  if (!${REGISTRATION_NAME}(env)) {
313    return false;
314  }
315""")
316
317  manual_registration = string.Template("""\
318// Step 3: Method declarations.
319
320${JNI_NATIVE_METHOD_ARRAY}\
321${PROXY_NATIVE_METHOD_ARRAY}\
322
323${JNI_NATIVE_METHOD}
324// Step 4: Registration function.
325
326namespace ${NAMESPACE} {
327
328bool RegisterNatives(JNIEnv* env) {\
329${REGISTER_PROXY_NATIVES}
330${REGISTER_NATIVES}
331  return true;
332}
333
334}  // namespace ${NAMESPACE}
335""")
336
337  short_name = options.use_proxy_hash or options.enable_jni_multiplexing
338  sub_dict = {
339      'ESCAPED_PROXY_CLASS':
340      common.escape_class_name(gen_jni_class.full_name_with_slashes),
341      'PROXY_CLASS':
342      gen_jni_class.full_name_with_slashes,
343      'KMETHODS':
344      registration_dict['PROXY_NATIVE_METHOD_ARRAY'],
345      'REGISTRATION_NAME':
346      jni_generator.GetRegistrationFunctionName(
347          gen_jni_class.full_name_with_slashes),
348  }
349
350  if registration_dict['PROXY_NATIVE_METHOD_ARRAY']:
351    proxy_native_array = registration_template.substitute(sub_dict)
352    proxy_natives_registration = registration_call.substitute(sub_dict)
353  else:
354    proxy_native_array = ''
355    proxy_natives_registration = ''
356
357  registration_dict['PROXY_NATIVE_METHOD_ARRAY'] = proxy_native_array
358  registration_dict['REGISTER_PROXY_NATIVES'] = proxy_natives_registration
359
360  if options.manual_jni_registration:
361    registration_dict['MANUAL_REGISTRATION'] = manual_registration.substitute(
362        registration_dict)
363  else:
364    registration_dict['MANUAL_REGISTRATION'] = ''
365
366
367def CreateProxyJavaFromDict(options,
368                            gen_jni_class,
369                            registration_dict,
370                            stub_methods='',
371                            forwarding=False):
372  template = string.Template("""\
373// Copyright 2018 The Chromium Authors
374// Use of this source code is governed by a BSD-style license that can be
375// found in the LICENSE file.
376
377package ${PACKAGE};
378
379// This file is autogenerated by
380//     third_party/jni_zero/jni_registration_generator.py
381// Please do not change its content.
382
383public class ${CLASS_NAME} {
384${FIELDS}
385${METHODS}
386}
387""")
388
389  if forwarding or not (options.use_proxy_hash
390                        or options.enable_jni_multiplexing):
391    fields = string.Template("""\
392    public static final boolean TESTING_ENABLED = ${TESTING_ENABLED};
393    public static final boolean REQUIRE_MOCK = ${REQUIRE_MOCK};
394""").substitute({
395        'TESTING_ENABLED': str(options.enable_proxy_mocks).lower(),
396        'REQUIRE_MOCK': str(options.require_mocks).lower(),
397    })
398  else:
399    fields = ''
400
401  if forwarding:
402    methods = registration_dict['FORWARDING_PROXY_METHODS']
403  else:
404    methods = registration_dict['PROXY_NATIVE_SIGNATURES']
405  methods += stub_methods
406
407  return template.substitute({
408      'CLASS_NAME': gen_jni_class.name,
409      'FIELDS': fields,
410      'PACKAGE': gen_jni_class.package_with_dots,
411      'METHODS': methods
412  })
413
414
415def CreateFromDict(options, registration_dict):
416  """Returns the content of the header file."""
417
418  template = string.Template("""\
419// Copyright 2017 The Chromium Authors
420// Use of this source code is governed by a BSD-style license that can be
421// found in the LICENSE file.
422
423
424// This file is autogenerated by
425//     third_party/jni_zero/jni_registration_generator.py
426// Please do not change its content.
427
428#ifndef ${HEADER_GUARD}
429#define ${HEADER_GUARD}
430
431#include <jni.h>
432
433#include <iterator>
434
435#include "third_party/jni_zero/jni_export.h"
436#include "third_party/jni_zero/jni_int_wrapper.h"
437#include "third_party/jni_zero/jni_zero_helper.h"
438
439
440// Step 1: Forward declarations (classes).
441${CLASS_PATH_DECLARATIONS}
442
443// Step 2: Forward declarations (methods).
444
445${FORWARD_DECLARATIONS}
446${FORWARDING_CALLS}
447${MANUAL_REGISTRATION}
448#endif  // ${HEADER_GUARD}
449""")
450  gen_jni_class = proxy.get_gen_jni_class(short=options.use_proxy_hash
451                                          or options.enable_jni_multiplexing,
452                                          name_prefix=options.module_name,
453                                          package_prefix=options.package_prefix)
454  _SetProxyRegistrationFields(options, gen_jni_class, registration_dict)
455  if not options.enable_jni_multiplexing:
456    registration_dict['FORWARDING_CALLS'] = ''
457  if len(registration_dict['FORWARD_DECLARATIONS']) == 0:
458    return ''
459
460  return template.substitute(registration_dict)
461
462
463def _GetJavaToNativeParamsList(param_types):
464  if not param_types:
465    return 'jlong switch_num'
466
467  # Parameters are named after their type, with a unique number per parameter
468  # type to make sure the names are unique, even within the same types.
469  params_type_count = collections.defaultdict(int)
470  params_in_stub = []
471  for t in param_types:
472    params_type_count[t] += 1
473    params_in_stub.append('%s %s_param%d' % (t.to_cpp(), t.to_java().replace(
474        '[]', '_array').lower(), params_type_count[t]))
475
476  return 'jlong switch_num, ' + ', '.join(params_in_stub)
477
478
479class DictionaryGenerator(object):
480  """Generates an inline header file for JNI registration."""
481  def __init__(self, jni_obj, options):
482    self.options = options
483    self.file_path = jni_obj.filename
484    self.content_namespace = jni_obj.jni_namespace
485    self.natives = jni_obj.natives
486    self.proxy_natives = jni_obj.proxy_natives
487    self.non_proxy_natives = jni_obj.non_proxy_natives
488    self.fully_qualified_class = jni_obj.java_class.full_name_with_slashes
489    self.type_resolver = jni_obj.type_resolver
490    self.class_name = jni_obj.java_class.name
491    self.helper = jni_generator.HeaderFileGeneratorHelper(
492        jni_obj.java_class,
493        module_name=jni_obj.module_name,
494        use_proxy_hash=options.use_proxy_hash,
495        package_prefix=options.package_prefix,
496        enable_jni_multiplexing=options.enable_jni_multiplexing)
497    self.registration_dict = None
498    self.gen_jni_class = proxy.get_gen_jni_class(
499        short=options.use_proxy_hash or options.enable_jni_multiplexing,
500        name_prefix=options.module_name,
501        package_prefix=options.package_prefix)
502
503  def Generate(self):
504    self.registration_dict = {
505        'FULL_CLASS_NAME': self.fully_qualified_class,
506        'FILE_PATH': self.file_path,
507    }
508    self._AddClassPathDeclarations()
509    self._AddForwardDeclaration()
510    self._AddJNINativeMethodsArrays()
511    self._AddProxyNativeMethodKStrings()
512    self._AddRegisterNativesCalls()
513    self._AddRegisterNativesFunctions()
514
515    self.registration_dict['PROXY_NATIVE_SIGNATURES'] = (''.join(
516        _MakeProxySignature(self.options, native)
517        for native in self.proxy_natives))
518
519    if self.options.enable_jni_multiplexing:
520      self._AssignSwitchNumberToNatives()
521      self._AddCases()
522
523    if self.options.use_proxy_hash or self.options.enable_jni_multiplexing:
524      self.registration_dict['FORWARDING_PROXY_METHODS'] = ('\n'.join(
525          _MakeForwardingProxy(self.options, self.gen_jni_class, native)
526          for native in self.proxy_natives))
527
528    return self.registration_dict
529
530  def _SetDictValue(self, key, value):
531    self.registration_dict[key] = jni_generator.WrapOutput(value)
532
533  def _AddClassPathDeclarations(self):
534    classes = self.helper.GetUniqueClasses(self.natives)
535    self._SetDictValue(
536        'CLASS_PATH_DECLARATIONS',
537        self.helper.GetClassPathLines(classes, declare_only=True))
538
539  def _AddForwardDeclaration(self):
540    """Add the content of the forward declaration to the dictionary."""
541    template = string.Template("""\
542JNI_BOUNDARY_EXPORT ${RETURN} ${STUB_NAME}(
543    JNIEnv* env,
544    ${PARAMS_IN_STUB});
545""")
546    forward_declaration = ''
547    for native in self.natives:
548      value = {
549          'RETURN': native.proxy_return_type.to_cpp(),
550          'STUB_NAME': self.helper.GetStubName(native),
551          'PARAMS_IN_STUB': jni_generator.GetParamsInStub(native),
552      }
553      forward_declaration += template.substitute(value)
554    self._SetDictValue('FORWARD_DECLARATIONS', forward_declaration)
555
556  def _AddRegisterNativesCalls(self):
557    """Add the body of the RegisterNativesImpl method to the dictionary."""
558
559    # Only register if there is at least 1 non-proxy native
560    if len(self.non_proxy_natives) == 0:
561      return ''
562
563    template = string.Template("""\
564  if (!${REGISTER_NAME}(env))
565    return false;
566""")
567    value = {
568        'REGISTER_NAME':
569        jni_generator.GetRegistrationFunctionName(self.fully_qualified_class)
570    }
571    register_body = template.substitute(value)
572    self._SetDictValue('REGISTER_NATIVES', register_body)
573
574  def _AddJNINativeMethodsArrays(self):
575    """Returns the implementation of the array of native methods."""
576    template = string.Template("""\
577static const JNINativeMethod kMethods_${JAVA_CLASS}[] = {
578${KMETHODS}
579};
580
581""")
582    open_namespace = ''
583    close_namespace = ''
584    if self.content_namespace:
585      parts = self.content_namespace.split('::')
586      all_namespaces = ['namespace %s {' % ns for ns in parts]
587      open_namespace = '\n'.join(all_namespaces) + '\n'
588      all_namespaces = ['}  // namespace %s' % ns for ns in parts]
589      all_namespaces.reverse()
590      close_namespace = '\n'.join(all_namespaces) + '\n\n'
591
592    body = self._SubstituteNativeMethods(template)
593    if body:
594      self._SetDictValue('JNI_NATIVE_METHOD_ARRAY', ''.join(
595          (open_namespace, body, close_namespace)))
596
597  def _GetKMethodsString(self, clazz):
598    if clazz != self.class_name:
599      return ''
600    ret = [self._GetKMethodArrayEntry(n) for n in self.non_proxy_natives]
601    return '\n'.join(ret)
602
603  def _GetKMethodArrayEntry(self, native):
604    template = string.Template('    { "${NAME}", "${JNI_DESCRIPTOR}", ' +
605                               'reinterpret_cast<void*>(${STUB_NAME}) },')
606
607    name = 'native' + native.cpp_name
608    jni_descriptor = native.proxy_signature.to_descriptor()
609    stub_name = self.helper.GetStubName(native)
610
611    if native.is_proxy:
612      # Literal name of the native method in the class that contains the actual
613      # native declaration.
614      if self.options.enable_jni_multiplexing:
615        class_name = common.escape_class_name(
616            self.gen_jni_class.full_name_with_slashes)
617        name = _GetMultiplexProxyName(native.proxy_signature)
618        proxy_signature = common.escape_class_name(name)
619        stub_name = 'Java_' + class_name + '_' + proxy_signature
620
621        multipliexed_signature = java_types.JavaSignature(
622            native.return_type, (java_types.LONG, ), None)
623        jni_descriptor = multipliexed_signature.to_descriptor()
624      elif self.options.use_proxy_hash:
625        name = native.hashed_proxy_name
626      else:
627        name = native.proxy_name
628    values = {
629        'NAME': name,
630        'JNI_DESCRIPTOR': jni_descriptor,
631        'STUB_NAME': stub_name
632    }
633    return template.substitute(values)
634
635  def _AddProxyNativeMethodKStrings(self):
636    """Returns KMethodString for wrapped native methods in all_classes """
637
638    proxy_k_strings = ('\n'.join(
639        self._GetKMethodArrayEntry(p) for p in self.proxy_natives))
640
641    self._SetDictValue('PROXY_NATIVE_METHOD_ARRAY', proxy_k_strings)
642
643  def _SubstituteNativeMethods(self, template):
644    """Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided
645    template."""
646    ret = []
647    all_classes = self.helper.GetUniqueClasses(self.natives)
648    all_classes[self.class_name] = self.fully_qualified_class
649
650    for clazz, full_clazz in all_classes.items():
651      if clazz == self.gen_jni_class.name:
652        continue
653
654      kmethods = self._GetKMethodsString(clazz)
655      namespace_str = ''
656      if self.content_namespace:
657        namespace_str = self.content_namespace + '::'
658      if kmethods:
659        values = {
660            'NAMESPACE': namespace_str,
661            'JAVA_CLASS': common.escape_class_name(full_clazz),
662            'KMETHODS': kmethods
663        }
664        ret += [template.substitute(values)]
665    if not ret: return ''
666    return '\n'.join(ret)
667
668  def GetJNINativeMethodsString(self):
669    """Returns the implementation of the array of native methods."""
670    template = string.Template("""\
671static const JNINativeMethod kMethods_${JAVA_CLASS}[] = {
672${KMETHODS}
673
674};
675""")
676    return self._SubstituteNativeMethods(template)
677
678  def _AddRegisterNativesFunctions(self):
679    """Returns the code for RegisterNatives."""
680    natives = self._GetRegisterNativesImplString()
681    if not natives:
682      return ''
683    template = string.Template("""\
684JNI_ZERO_COMPONENT_BUILD_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) {
685${NATIVES}\
686  return true;
687}
688
689""")
690    values = {
691        'REGISTER_NAME':
692        jni_generator.GetRegistrationFunctionName(self.fully_qualified_class),
693        'NATIVES':
694        natives
695    }
696    self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values))
697
698  def _GetRegisterNativesImplString(self):
699    """Returns the shared implementation for RegisterNatives."""
700    template = string.Template("""\
701  const int kMethods_${JAVA_CLASS}Size =
702      std::size(${NAMESPACE}kMethods_${JAVA_CLASS});
703  if (env->RegisterNatives(
704      ${JAVA_CLASS}_clazz(env),
705      ${NAMESPACE}kMethods_${JAVA_CLASS},
706      kMethods_${JAVA_CLASS}Size) < 0) {
707    jni_generator::HandleRegistrationError(env,
708        ${JAVA_CLASS}_clazz(env),
709        __FILE__);
710    return false;
711  }
712
713""")
714    # Only register if there is a native method not in a proxy,
715    # since all the proxies will be registered together.
716    if len(self.non_proxy_natives) != 0:
717      return self._SubstituteNativeMethods(template)
718    return ''
719
720  def _AssignSwitchNumberToNatives(self):
721    # The switch number for a native method is a 64-bit long with the first
722    # bit being a sign digit. The signed two's complement is taken when
723    # appropriate to make use of negative numbers.
724    for native in self.proxy_natives:
725      hashed_long = hashlib.md5(
726          native.proxy_name.encode('utf-8')).hexdigest()[:16]
727      switch_num = int(hashed_long, 16)
728      if (switch_num & 1 << 63):
729        switch_num -= (1 << 64)
730
731      native.switch_num = str(switch_num)
732
733  def _AddCases(self):
734    # Switch cases are grouped together by the same proxy signatures.
735    template = string.Template("""
736          case ${SWITCH_NUM}:
737            return ${STUB_NAME}(env, jcaller${PARAMS});
738          """)
739
740    signature_to_cases = collections.defaultdict(list)
741    for native in self.proxy_natives:
742      signature = native.proxy_signature
743      params = _GetParamsListForMultiplex(native.proxy_params, with_types=False)
744      values = {
745          'SWITCH_NUM': native.switch_num,
746          # We are forced to call the generated stub instead of the impl because
747          # the impl is not guaranteed to have a globally unique name.
748          'STUB_NAME': self.helper.GetStubName(native),
749          'PARAMS': params,
750      }
751      signature_to_cases[signature].append(template.substitute(values))
752
753    self.registration_dict['SIGNATURE_TO_CASES'] = signature_to_cases
754
755
756def _GetParamsListForMultiplex(params, *, with_types):
757  if not params:
758    return ''
759
760  # Parameters are named after their type, with a unique number per parameter
761  # type to make sure the names are unique, even within the same types.
762  params_type_count = collections.defaultdict(int)
763  sb = []
764  for p in params:
765    type_str = p.java_type.to_java()
766    params_type_count[type_str] += 1
767    param_type = f'{type_str} ' if with_types else ''
768    sb.append('%s%s_param%d' % (param_type, type_str.replace(
769        '[]', '_array').lower(), params_type_count[type_str]))
770
771  return ', ' + ', '.join(sb)
772
773
774_MULTIPLEXED_CHAR_BY_TYPE = {
775    '[]': 'A',
776    'byte': 'B',
777    'char': 'C',
778    'double': 'D',
779    'float': 'F',
780    'int': 'I',
781    'long': 'J',
782    'Class': 'L',
783    'Object': 'O',
784    'String': 'R',
785    'short': 'S',
786    'Throwable': 'T',
787    'boolean': 'Z',
788}
789
790
791def _GetMultiplexProxyName(signature):
792  # Proxy signatures for methods are named after their return type and
793  # parameters to ensure uniqueness, even for the same return types.
794  params_part = ''
795  params_list = [t.to_java() for t in signature.param_types]
796  # Parameter types could contain multi-dimensional arrays and every
797  # instance of [] has to be replaced in the proxy signature name.
798  for k, v in _MULTIPLEXED_CHAR_BY_TYPE.items():
799    params_list = [p.replace(k, v) for p in params_list]
800  params_part = ''
801  if params_list:
802    params_part = '_' + ''.join(p for p in params_list)
803
804  java_return_type = signature.return_type.to_java()
805  return_value_part = java_return_type.replace('[]', '_array').lower()
806  return 'resolve_for_' + return_value_part + params_part
807
808
809def _MakeForwardingProxy(options, gen_jni_class, proxy_native):
810  template = string.Template("""
811    public static ${RETURN_TYPE} ${METHOD_NAME}(${PARAMS_WITH_TYPES}) {
812        ${MAYBE_RETURN}${PROXY_CLASS}.${PROXY_METHOD_NAME}(${PARAM_NAMES});
813    }""")
814
815  param_names = proxy_native.proxy_params.to_call_str()
816
817  if options.enable_jni_multiplexing:
818    if not param_names:
819      param_names = proxy_native.switch_num + 'L'
820    else:
821      param_names = proxy_native.switch_num + 'L, ' + param_names
822    proxy_method_name = _GetMultiplexProxyName(proxy_native.proxy_signature)
823  else:
824    proxy_method_name = proxy_native.hashed_proxy_name
825
826  return template.substitute({
827      'RETURN_TYPE':
828      proxy_native.proxy_return_type.to_java(),
829      'METHOD_NAME':
830      proxy_native.proxy_name,
831      'PARAMS_WITH_TYPES':
832      proxy_native.proxy_params.to_java_declaration(),
833      'MAYBE_RETURN':
834      '' if proxy_native.proxy_return_type.is_void() else 'return ',
835      'PROXY_CLASS':
836      gen_jni_class.full_name_with_dots,
837      'PROXY_METHOD_NAME':
838      proxy_method_name,
839      'PARAM_NAMES':
840      param_names,
841  })
842
843
844def _MakeProxySignature(options, proxy_native):
845  params_with_types = proxy_native.proxy_params.to_java_declaration()
846  native_method_line = """
847    public static native ${RETURN} ${PROXY_NAME}(${PARAMS_WITH_TYPES});"""
848
849  if options.enable_jni_multiplexing:
850    # This has to be only one line and without comments because all the proxy
851    # signatures will be joined, then split on new lines with duplicates removed
852    # since multiple |proxy_native|s map to the same multiplexed signature.
853    signature_template = string.Template(native_method_line)
854
855    alt_name = None
856    proxy_name = _GetMultiplexProxyName(proxy_native.proxy_signature)
857    params_with_types = 'long switch_num' + _GetParamsListForMultiplex(
858        proxy_native.proxy_params, with_types=True)
859  elif options.use_proxy_hash:
860    signature_template = string.Template("""
861      // Original name: ${ALT_NAME}""" + native_method_line)
862
863    alt_name = proxy_native.proxy_name
864    proxy_name = proxy_native.hashed_proxy_name
865  else:
866    signature_template = string.Template("""
867      // Hashed name: ${ALT_NAME}""" + native_method_line)
868
869    # We add the prefix that is sometimes used so that codesearch can find it if
870    # someone searches a full method name from the stacktrace.
871    alt_name = f'Java_J_N_{proxy_native.hashed_proxy_name}'
872    proxy_name = proxy_native.proxy_name
873
874  return_type_str = proxy_native.proxy_return_type.to_java()
875  return signature_template.substitute({
876      'ALT_NAME': alt_name,
877      'RETURN': return_type_str,
878      'PROXY_NAME': proxy_name,
879      'PARAMS_WITH_TYPES': params_with_types,
880  })
881
882
883def _ParseSourceList(path):
884  # Path can have duplicates.
885  with open(path) as f:
886    return sorted(set(f.read().splitlines()))
887
888
889def _write_depfile(depfile_path, first_gn_output, inputs):
890  def _process_path(path):
891    assert not os.path.isabs(path), f'Found abs path in depfile: {path}'
892    if os.path.sep != posixpath.sep:
893      path = str(pathlib.Path(path).as_posix())
894    assert '\\' not in path, f'Found \\ in depfile: {path}'
895    return path.replace(' ', '\\ ')
896
897  sb = []
898  sb.append(_process_path(first_gn_output))
899  if inputs:
900    # Sort and uniquify to ensure file is hermetic.
901    # One path per line to keep it human readable.
902    sb.append(': \\\n ')
903    sb.append(' \\\n '.join(sorted(_process_path(p) for p in set(inputs))))
904  else:
905    sb.append(': ')
906  sb.append('\n')
907
908  pathlib.Path(depfile_path).write_text(''.join(sb))
909
910
911def main(parser, args):
912  if not args.enable_proxy_mocks and args.require_mocks:
913    parser.error('--require-mocks requires --enable-proxy-mocks.')
914  if not args.header_path and args.manual_jni_registration:
915    parser.error('--manual-jni-registration requires --header-path.')
916  if args.remove_uncalled_methods and not args.native_sources_file:
917    parser.error('--remove-uncalled-methods requires --native-sources-file.')
918
919  java_sources = _ParseSourceList(args.java_sources_file)
920  if args.native_sources_file:
921    native_sources = _ParseSourceList(args.native_sources_file)
922  else:
923    if args.add_stubs_for_missing_native:
924      # This will create a fully stubbed out GEN_JNI.
925      native_sources = []
926    else:
927      # Just treating it like we have perfect alignment between native and java
928      # when only looking at java.
929      native_sources = java_sources
930
931  _Generate(args, native_sources, java_sources=java_sources)
932
933  if args.depfile:
934    # GN does not declare a dep on the sources files to avoid circular
935    # dependencies, so they need to be listed here.
936    all_inputs = native_sources + java_sources + [args.java_sources_file]
937    if args.native_sources_file:
938      all_inputs.append(args.native_sources_file)
939    _write_depfile(args.depfile, args.srcjar_path, all_inputs)
940