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