xref: /aosp_15_r20/external/cronet/third_party/jni_zero/jni_registration_generator.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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 itertools
9import multiprocessing
10import os
11import pathlib
12import pickle
13import posixpath
14import re
15import string
16import sys
17import zipfile
18
19from codegen import header_common
20from codegen import natives_header
21import common
22import java_types
23import jni_generator
24import parse
25import proxy
26
27
28_SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN = "<INSERT HERE>"
29
30# All but FULL_CLASS_NAME, which is used only for sorting.
31MERGEABLE_KEYS = [
32    'CLASS_ACCESSORS',
33    'FORWARD_DECLARATIONS',
34    'JNI_NATIVE_METHOD',
35    'JNI_NATIVE_METHOD_ARRAY',
36    'PROXY_NATIVE_SIGNATURES',
37    'FORWARDING_PROXY_METHODS',
38    'PROXY_NATIVE_METHOD_ARRAY',
39    'REGISTER_NATIVES',
40]
41
42
43def _ParseHelper(package_prefix, path):
44  return parse.parse_java_file(path, package_prefix=package_prefix)
45
46
47def _LoadJniObjs(paths, options):
48  ret = {}
49  if all(p.endswith('.jni.pickle') for p in paths):
50    for pickle_path in paths:
51      with open(pickle_path, 'rb') as f:
52        parsed_files = pickle.load(f)
53      ret[pickle_path] = [
54          jni_generator.JniObject(pf, options, from_javap=False)
55          for pf in parsed_files
56      ]
57  else:
58    func = functools.partial(_ParseHelper, options.package_prefix)
59    with multiprocessing.Pool() as pool:
60      for pf in pool.imap_unordered(func, paths):
61        ret[pf.filename] = [
62            jni_generator.JniObject(pf, options, from_javap=False)
63        ]
64
65  return ret
66
67
68def _FilterJniObjs(jni_objs_by_path, options):
69  for jni_objs in jni_objs_by_path.values():
70    # Remove test-only methods.
71    if not options.include_test_only:
72      for jni_obj in jni_objs:
73        jni_obj.RemoveTestOnlyNatives()
74    # Ignoring non-active modules and empty natives lists.
75    jni_objs[:] = [
76        o for o in jni_objs
77        if o.natives and o.module_name == options.module_name
78    ]
79
80
81def _Flatten(jni_objs_by_path, paths):
82  return itertools.chain(*(jni_objs_by_path[p] for p in paths))
83
84
85def _Generate(options, native_sources, java_sources):
86  """Generates files required to perform JNI registration.
87
88  Generates a srcjar containing a single class, GEN_JNI, that contains all
89  native method declarations.
90
91  Optionally generates a header file that provides RegisterNatives to perform
92  JNI registration.
93
94  Args:
95    options: arguments from the command line
96    native_sources: A list of .jni.pickle or .java file paths taken from native
97        dependency tree. The source of truth.
98    java_sources: A list of .jni.pickle or .java file paths. Used to assert
99        against native_sources.
100  """
101  # The native-based sources are the "source of truth" - the Java based ones
102  # will be used later to generate stubs and make assertions.
103  jni_objs_by_path = _LoadJniObjs(set(native_sources + java_sources), options)
104  _FilterJniObjs(jni_objs_by_path, options)
105
106  dicts = []
107  for jni_obj in _Flatten(jni_objs_by_path, native_sources):
108    dicts.append(DictionaryGenerator(jni_obj, options).Generate())
109  # Sort to make output deterministic.
110  dicts.sort(key=lambda d: d['FULL_CLASS_NAME'])
111
112  stubs = _GenerateStubsAndAssert(options, jni_objs_by_path, native_sources,
113                                  java_sources)
114  combined_dict = {}
115  for key in MERGEABLE_KEYS:
116    combined_dict[key] = ''.join(d.get(key, '') for d in dicts)
117
118  short_gen_jni_class = proxy.get_gen_jni_class(
119      short=True,
120      name_prefix=options.module_name,
121      package_prefix=options.package_prefix)
122  full_gen_jni_class = proxy.get_gen_jni_class(
123      short=False,
124      name_prefix=options.module_name,
125      package_prefix=options.package_prefix)
126  if options.use_proxy_hash or options.enable_jni_multiplexing:
127    gen_jni_class = short_gen_jni_class
128  else:
129    gen_jni_class = full_gen_jni_class
130  # PROXY_NATIVE_SIGNATURES and PROXY_NATIVE_METHOD_ARRAY will have
131  # duplicates for JNI multiplexing since all native methods with similar
132  # signatures map to the same proxy. Similarly, there may be multiple switch
133  # case entries for the same proxy signatures.
134  if options.enable_jni_multiplexing:
135    proxy_signatures_list = sorted(
136        set(combined_dict['PROXY_NATIVE_SIGNATURES'].split('\n')))
137    combined_dict['PROXY_NATIVE_SIGNATURES'] = '\n'.join(
138        signature for signature in proxy_signatures_list)
139
140    proxy_native_array_list = sorted(
141        set(combined_dict['PROXY_NATIVE_METHOD_ARRAY'].split('},\n')))
142    combined_dict['PROXY_NATIVE_METHOD_ARRAY'] = '},\n'.join(
143        p for p in proxy_native_array_list if p != '') + '}'
144    signature_to_cases = collections.defaultdict(list)
145    for d in dicts:
146      for signature, cases in d['SIGNATURE_TO_CASES'].items():
147        signature_to_cases[signature].extend(cases)
148    combined_dict[
149        'FORWARDING_PROXY_METHODS'] = _InsertMultiplexingSwitchNumbers(
150            signature_to_cases, combined_dict['FORWARDING_PROXY_METHODS'],
151            short_gen_jni_class)
152    combined_dict['FORWARDING_CALLS'] = _AddForwardingCalls(
153        signature_to_cases, short_gen_jni_class)
154
155  if options.header_path:
156    combined_dict['NAMESPACE'] = options.namespace or ''
157    header_content = CreateFromDict(gen_jni_class, options, combined_dict)
158    with common.atomic_output(options.header_path, mode='w') as f:
159      f.write(header_content)
160
161  stub_methods_string = ''.join(stubs)
162
163  with common.atomic_output(options.srcjar_path) as f:
164    with zipfile.ZipFile(f, 'w') as srcjar:
165      if options.use_proxy_hash or options.enable_jni_multiplexing:
166        # J/N.java
167        common.add_to_zip_hermetic(
168            srcjar,
169            f'{short_gen_jni_class.full_name_with_slashes}.java',
170            data=CreateProxyJavaFromDict(options, gen_jni_class, combined_dict))
171        # org/jni_zero/GEN_JNI.java
172        common.add_to_zip_hermetic(
173            srcjar,
174            f'{full_gen_jni_class.full_name_with_slashes}.java',
175            data=CreateProxyJavaFromDict(options,
176                                         full_gen_jni_class,
177                                         combined_dict,
178                                         stub_methods=stub_methods_string,
179                                         forwarding=True))
180      else:
181        # org/jni_zero/GEN_JNI.java
182        common.add_to_zip_hermetic(
183            srcjar,
184            f'{full_gen_jni_class.full_name_with_slashes}.java',
185            data=CreateProxyJavaFromDict(options,
186                                         gen_jni_class,
187                                         combined_dict,
188                                         stub_methods=stub_methods_string))
189
190
191def _GenerateStubsAndAssert(options, jni_objs_by_path, native_sources,
192                            java_sources):
193  native_sources_set = set(native_sources)
194  java_sources_set = set(java_sources)
195  native_only = native_sources_set - java_sources_set
196  java_only = java_sources_set - native_sources_set
197
198  java_only_jni_objs = sorted(_Flatten(jni_objs_by_path, java_only),
199                              key=lambda jni_obj: jni_obj.filename)
200  native_only_jni_objs = sorted(_Flatten(jni_objs_by_path, native_only),
201                                key=lambda jni_obj: jni_obj.filename)
202  failed = False
203  if not options.add_stubs_for_missing_native and java_only_jni_objs:
204    failed = True
205    warning_message = '''Failed JNI assertion!
206We reference Java files which use JNI, but our native library does not depend on
207the corresponding generate_jni().
208To bypass this check, add stubs to Java with --add-stubs-for-missing-jni.
209Excess Java files:
210'''
211    sys.stderr.write(warning_message)
212    sys.stderr.write('\n'.join(jni_obj.filename
213                               for jni_obj in java_only_jni_objs))
214    sys.stderr.write('\n')
215  if not options.remove_uncalled_methods and native_only_jni_objs:
216    failed = True
217    warning_message = '''Failed JNI assertion!
218Our native library depends on generate_jnis which reference Java files that we
219do not include in our final dex.
220To bypass this check, delete these extra methods with --remove-uncalled-jni.
221Unneeded Java files:
222'''
223    sys.stderr.write(warning_message)
224    sys.stderr.write('\n'.join(native_only_jni_objs.filename
225                               for jni_obj in native_only_jni_objs))
226    sys.stderr.write('\n')
227  if failed:
228    sys.exit(1)
229
230  return [
231      _GenerateStubs(jni_obj.proxy_natives) for jni_obj in java_only_jni_objs
232  ]
233
234
235def _GenerateStubs(natives):
236  final_string = ''
237  for native in natives:
238    template = string.Template("""
239
240    public static ${RETURN_TYPE} ${METHOD_NAME}(${PARAMS_WITH_TYPES}) {
241        throw new RuntimeException("Stub - not implemented!");
242    }""")
243
244    final_string += template.substitute({
245        'RETURN_TYPE':
246        native.proxy_return_type.to_java(),
247        'METHOD_NAME':
248        native.proxy_name,
249        'PARAMS_WITH_TYPES':
250        native.proxy_params.to_java_declaration(),
251    })
252  return final_string
253
254
255def _InsertMultiplexingSwitchNumbers(signature_to_cases, java_functions_string,
256                                     short_gen_jni_class):
257  switch_case_method_name_re = re.compile('return (\w+)\(')
258  java_function_call_re = re.compile('public static \S+ (\w+)\(')
259  method_to_switch_num = {}
260  for signature, cases in sorted(signature_to_cases.items()):
261    for i, case in enumerate(cases):
262      assert _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN in case
263      method_name = switch_case_method_name_re.search(case).group(1)
264      method_to_switch_num[method_name] = i
265      cases[i] = case.replace(_SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN, str(i))
266
267  swaps = {}
268  for match in java_function_call_re.finditer(java_functions_string):
269    unhashed_java_name = match.group(1)
270    is_test_only = jni_generator.NameIsTestOnly(unhashed_java_name)
271    hashed = proxy.create_hashed_method_name(unhashed_java_name, is_test_only)
272    fully_qualified_hash = f'{short_gen_jni_class.full_name_with_slashes}/{hashed}'
273    cpp_hash_name = 'Java_' + common.escape_class_name(fully_qualified_hash)
274    switch_num = method_to_switch_num[cpp_hash_name]
275    replace_location = java_functions_string.find(
276        _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN, match.end())
277    swaps[replace_location] = switch_num
278
279  # Doing a seperate pass to construct the new string for efficiency - don't
280  # want to do thousands of copies of a massive string.
281  new_java_functions_string = ""
282  prev_loc = 0
283  for loc, num in sorted(swaps.items()):
284    new_java_functions_string += java_functions_string[prev_loc:loc]
285    new_java_functions_string += str(num)
286    prev_loc = loc + len(_SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN)
287  new_java_functions_string += java_functions_string[prev_loc:]
288  return new_java_functions_string
289
290
291
292
293def _AddForwardingCalls(signature_to_cases, short_gen_jni_class):
294  template = string.Template("""
295JNI_BOUNDARY_EXPORT ${RETURN} Java_${CLASS_NAME}_${PROXY_SIGNATURE}(
296    JNIEnv* env,
297    jclass jcaller,
298    ${PARAMS_IN_STUB}) {
299        switch (switch_num) {
300          ${CASES}
301          default:
302            JNI_ZERO_ELOG("${CLASS_NAME}_${PROXY_SIGNATURE} was called with an \
303invalid switch number: %d", switch_num);
304            JNI_ZERO_DCHECK(false);
305            return${DEFAULT_RETURN};
306        }
307}""")
308
309  switch_statements = []
310  for signature, cases in sorted(signature_to_cases.items()):
311    params_in_stub = _GetJavaToNativeParamsList(signature.param_types)
312    switch_statements.append(
313        template.substitute({
314            'RETURN':
315            signature.return_type.to_cpp(),
316            'CLASS_NAME':
317            common.escape_class_name(
318                short_gen_jni_class.full_name_with_slashes),
319            'PROXY_SIGNATURE':
320            common.escape_class_name(_GetMultiplexProxyName(signature)),
321            'PARAMS_IN_STUB':
322            params_in_stub,
323            'CASES':
324            ''.join(cases),
325            'DEFAULT_RETURN':
326            '' if signature.return_type.is_void() else ' {}',
327        }))
328
329  return ''.join(s for s in switch_statements)
330
331
332def _SetProxyRegistrationFields(options, gen_jni_class, registration_dict):
333  registration_template = string.Template("""\
334
335static const JNINativeMethod kMethods_${ESCAPED_PROXY_CLASS}[] = {
336${KMETHODS}
337};
338
339namespace {
340
341JNI_ZERO_COMPONENT_BUILD_EXPORT bool ${REGISTRATION_NAME}(JNIEnv* env) {
342  const int number_of_methods = std::size(kMethods_${ESCAPED_PROXY_CLASS});
343
344  jni_zero::ScopedJavaLocalRef<jclass> native_clazz =
345      jni_zero::GetClass(env, "${PROXY_CLASS}");
346  if (env->RegisterNatives(
347      native_clazz.obj(),
348      kMethods_${ESCAPED_PROXY_CLASS},
349      number_of_methods) < 0) {
350
351    jni_zero::internal::HandleRegistrationError(env, native_clazz.obj(), __FILE__);
352    return false;
353  }
354
355  return true;
356}
357
358}  // namespace
359""")
360
361  registration_call = string.Template("""\
362
363  // Register natives in a proxy.
364  if (!${REGISTRATION_NAME}(env)) {
365    return false;
366  }
367""")
368
369  manual_registration = string.Template("""\
370// Method declarations.
371
372${JNI_NATIVE_METHOD_ARRAY}\
373${PROXY_NATIVE_METHOD_ARRAY}\
374
375${JNI_NATIVE_METHOD}
376// Registration function.
377
378namespace ${NAMESPACE} {
379
380bool RegisterNatives(JNIEnv* env) {\
381${REGISTER_PROXY_NATIVES}
382${REGISTER_NATIVES}
383  return true;
384}
385
386}  // namespace ${NAMESPACE}
387""")
388
389  short_name = options.use_proxy_hash or options.enable_jni_multiplexing
390  sub_dict = {
391      'ESCAPED_PROXY_CLASS':
392      common.escape_class_name(gen_jni_class.full_name_with_slashes),
393      'PROXY_CLASS':
394      gen_jni_class.full_name_with_slashes.replace("/", "."),
395      'KMETHODS':
396      registration_dict['PROXY_NATIVE_METHOD_ARRAY'],
397      'REGISTRATION_NAME':
398      _GetRegistrationFunctionName(gen_jni_class.full_name_with_slashes),
399  }
400
401  if registration_dict['PROXY_NATIVE_METHOD_ARRAY']:
402    proxy_native_array = registration_template.substitute(sub_dict)
403    proxy_natives_registration = registration_call.substitute(sub_dict)
404  else:
405    proxy_native_array = ''
406    proxy_natives_registration = ''
407
408  registration_dict['PROXY_NATIVE_METHOD_ARRAY'] = proxy_native_array
409  registration_dict['REGISTER_PROXY_NATIVES'] = proxy_natives_registration
410
411  if options.manual_jni_registration:
412    registration_dict['MANUAL_REGISTRATION'] = manual_registration.substitute(
413        registration_dict)
414  else:
415    registration_dict['MANUAL_REGISTRATION'] = ''
416
417
418def CreateProxyJavaFromDict(options,
419                            gen_jni_class,
420                            registration_dict,
421                            stub_methods='',
422                            forwarding=False):
423  template = string.Template("""\
424// Copyright 2018 The Chromium Authors
425// Use of this source code is governed by a BSD-style license that can be
426// found in the LICENSE file.
427
428package ${PACKAGE};
429
430// This file is autogenerated by
431//     third_party/jni_zero/jni_registration_generator.py
432// Please do not change its content.
433
434public class ${CLASS_NAME} {
435${FIELDS}
436${METHODS}
437}
438""")
439
440  if forwarding or not (options.use_proxy_hash
441                        or options.enable_jni_multiplexing):
442    fields = string.Template("""\
443    public static final boolean TESTING_ENABLED = ${TESTING_ENABLED};
444    public static final boolean REQUIRE_MOCK = ${REQUIRE_MOCK};
445""").substitute({
446        'TESTING_ENABLED': str(options.enable_proxy_mocks).lower(),
447        'REQUIRE_MOCK': str(options.require_mocks).lower(),
448    })
449  else:
450    fields = ''
451
452  if forwarding:
453    methods = registration_dict['FORWARDING_PROXY_METHODS']
454  else:
455    methods = registration_dict['PROXY_NATIVE_SIGNATURES']
456  methods += stub_methods
457
458  return template.substitute({
459      'CLASS_NAME': gen_jni_class.name,
460      'FIELDS': fields,
461      'PACKAGE': gen_jni_class.package_with_dots,
462      'METHODS': methods
463  })
464
465
466def CreateFromDict(gen_jni_class, options, registration_dict):
467  """Returns the content of the header file."""
468  header_guard = os.path.splitext(options.header_path)[0].upper() + '_'
469  header_guard = re.sub(r'[/.-]', '_', header_guard)
470
471  preamble, epilogue = header_common.header_preamble(
472      jni_generator.GetScriptName(),
473      gen_jni_class,
474      system_includes=['iterator'],  # For std::size().
475      user_includes=['third_party/jni_zero/jni_zero_internal.h'],
476      header_guard=header_guard)
477  registration_dict['PREAMBLE'] = preamble
478  registration_dict['EPILOGUE'] = epilogue
479  template = string.Template("""\
480${PREAMBLE}
481
482${CLASS_ACCESSORS}
483// Forward declarations (methods).
484
485${FORWARD_DECLARATIONS}
486${FORWARDING_CALLS}
487${MANUAL_REGISTRATION}
488${EPILOGUE}
489""")
490  _SetProxyRegistrationFields(options, gen_jni_class, registration_dict)
491  if not options.enable_jni_multiplexing:
492    registration_dict['FORWARDING_CALLS'] = ''
493  if len(registration_dict['FORWARD_DECLARATIONS']) == 0:
494    return ''
495
496  return template.substitute(registration_dict)
497
498
499def _GetJavaToNativeParamsList(param_types):
500  if not param_types:
501    return 'jint switch_num'
502
503  # Parameters are named after their type, with a unique number per parameter
504  # type to make sure the names are unique, even within the same types.
505  params_type_count = collections.defaultdict(int)
506  params_in_stub = []
507  for t in param_types:
508    params_type_count[t] += 1
509    params_in_stub.append('%s %s_param%d' % (t.to_cpp(), t.to_java().replace(
510        '[]', '_array').lower(), params_type_count[t]))
511
512  return 'jint switch_num, ' + ', '.join(params_in_stub)
513
514
515def _GetRegistrationFunctionName(fully_qualified_class):
516  """Returns the register name with a given class."""
517  return 'RegisterNative_' + common.escape_class_name(fully_qualified_class)
518
519
520class DictionaryGenerator(object):
521  """Generates an inline header file for JNI registration."""
522  def __init__(self, jni_obj, options):
523    self.options = options
524    self.file_path = jni_obj.filename
525    self.content_namespace = jni_obj.jni_namespace
526    self.natives = jni_obj.natives
527    self.proxy_natives = jni_obj.proxy_natives
528    self.non_proxy_natives = jni_obj.non_proxy_natives
529    self.fully_qualified_class = jni_obj.java_class.full_name_with_slashes
530    self.type_resolver = jni_obj.type_resolver
531    self.class_name = jni_obj.java_class.name
532    self.registration_dict = None
533    self.jni_obj = jni_obj
534    self.gen_jni_class = proxy.get_gen_jni_class(
535        short=options.use_proxy_hash or options.enable_jni_multiplexing,
536        name_prefix=options.module_name,
537        package_prefix=options.package_prefix)
538
539  def Generate(self):
540    # GEN_JNI is handled separately.
541    java_classes_with_natives = sorted(
542        set(n.java_class for n in self.jni_obj.non_proxy_natives))
543
544    self.registration_dict = {
545        'FULL_CLASS_NAME': self.fully_qualified_class,
546        'FILE_PATH': self.file_path,
547    }
548    self.registration_dict['CLASS_ACCESSORS'] = (header_common.class_accessors(
549        java_classes_with_natives, self.jni_obj.module_name))
550
551    self._AddForwardDeclaration()
552    self._AddJNINativeMethodsArrays(java_classes_with_natives)
553    self._AddProxyNativeMethodKStrings()
554    self._AddRegisterNativesCalls()
555    self._AddRegisterNativesFunctions(java_classes_with_natives)
556
557    self.registration_dict['PROXY_NATIVE_SIGNATURES'] = (''.join(
558        _MakeProxySignature(self.options, native)
559        for native in self.proxy_natives))
560
561    if self.options.enable_jni_multiplexing:
562      self._AddCases()
563
564    if self.options.use_proxy_hash or self.options.enable_jni_multiplexing:
565      self.registration_dict['FORWARDING_PROXY_METHODS'] = ('\n'.join(
566          _MakeForwardingProxy(self.options, self.gen_jni_class, native)
567          for native in self.proxy_natives))
568
569    return self.registration_dict
570
571  def _SetDictValue(self, key, value):
572    self.registration_dict[key] = jni_generator.WrapOutput(value)
573
574  def _AddForwardDeclaration(self):
575    """Add the content of the forward declaration to the dictionary."""
576    sb = common.StringBuilder()
577    for native in self.natives:
578      with sb.statement():
579        natives_header.proxy_declaration(sb, self.jni_obj, native)
580    self._SetDictValue('FORWARD_DECLARATIONS', sb.to_string())
581
582  def _AddRegisterNativesCalls(self):
583    """Add the body of the RegisterNativesImpl method to the dictionary."""
584
585    # Only register if there is at least 1 non-proxy native
586    if len(self.non_proxy_natives) == 0:
587      return ''
588
589    template = string.Template("""\
590  if (!${REGISTER_NAME}(env))
591    return false;
592""")
593    value = {
594        'REGISTER_NAME':
595        _GetRegistrationFunctionName(self.fully_qualified_class)
596    }
597    register_body = template.substitute(value)
598    self._SetDictValue('REGISTER_NATIVES', register_body)
599
600  def _AddJNINativeMethodsArrays(self, java_classes_with_natives):
601    """Returns the implementation of the array of native methods."""
602    template = string.Template("""\
603static const JNINativeMethod kMethods_${JAVA_CLASS}[] = {
604${KMETHODS}
605};
606
607""")
608    open_namespace = ''
609    close_namespace = ''
610    if self.content_namespace:
611      parts = self.content_namespace.split('::')
612      all_namespaces = ['namespace %s {' % ns for ns in parts]
613      open_namespace = '\n'.join(all_namespaces) + '\n'
614      all_namespaces = ['}  // namespace %s' % ns for ns in parts]
615      all_namespaces.reverse()
616      close_namespace = '\n'.join(all_namespaces) + '\n\n'
617
618    body = self._SubstituteNativeMethods(java_classes_with_natives, template)
619    if body:
620      self._SetDictValue('JNI_NATIVE_METHOD_ARRAY', ''.join(
621          (open_namespace, body, close_namespace)))
622
623  def _GetKMethodsString(self, clazz):
624    if clazz != self.class_name:
625      return ''
626    ret = [self._GetKMethodArrayEntry(n) for n in self.non_proxy_natives]
627    return '\n'.join(ret)
628
629  def _GetKMethodArrayEntry(self, native):
630    template = string.Template('    { "${NAME}", "${JNI_DESCRIPTOR}", ' +
631                               'reinterpret_cast<void*>(${STUB_NAME}) },')
632
633    name = 'native' + native.cpp_name
634    jni_descriptor = native.proxy_signature.to_descriptor()
635    stub_name = self.jni_obj.GetStubName(native)
636
637    if native.is_proxy:
638      # Literal name of the native method in the class that contains the actual
639      # native declaration.
640      if self.options.enable_jni_multiplexing:
641        class_name = common.escape_class_name(
642            self.gen_jni_class.full_name_with_slashes)
643        name = _GetMultiplexProxyName(native.proxy_signature)
644        proxy_signature = common.escape_class_name(name)
645        stub_name = 'Java_' + class_name + '_' + proxy_signature
646
647        multipliexed_signature = java_types.JavaSignature(
648            native.return_type, (java_types.LONG, ), None)
649        jni_descriptor = multipliexed_signature.to_descriptor()
650      elif self.options.use_proxy_hash:
651        name = native.hashed_proxy_name
652      else:
653        name = native.proxy_name
654    values = {
655        'NAME': name,
656        'JNI_DESCRIPTOR': jni_descriptor,
657        'STUB_NAME': stub_name
658    }
659    return template.substitute(values)
660
661  def _AddProxyNativeMethodKStrings(self):
662    """Returns KMethodString for wrapped native methods in all_classes """
663
664    proxy_k_strings = ('\n'.join(
665        self._GetKMethodArrayEntry(p) for p in self.proxy_natives))
666
667    self._SetDictValue('PROXY_NATIVE_METHOD_ARRAY', proxy_k_strings)
668
669  def _SubstituteNativeMethods(self, java_classes, template):
670    """Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided
671    template."""
672    ret = []
673
674    for java_class in java_classes:
675      clazz = java_class.name
676      full_clazz = java_class.full_name_with_slashes
677
678      kmethods = self._GetKMethodsString(clazz)
679      namespace_str = ''
680      if self.content_namespace:
681        namespace_str = self.content_namespace + '::'
682      if kmethods:
683        values = {
684            'NAMESPACE':
685            namespace_str,
686            'JAVA_CLASS':
687            common.escape_class_name(full_clazz),
688            'JAVA_CLASS_ACCESSOR':
689            header_common.class_accessor_expression(java_class),
690            'KMETHODS':
691            kmethods
692        }
693        ret += [template.substitute(values)]
694    return '\n'.join(ret)
695
696  def _AddRegisterNativesFunctions(self, java_classes_with_natives):
697    """Returns the code for RegisterNatives."""
698    if not java_classes_with_natives:
699      return ''
700    natives = self._GetRegisterNativesImplString(java_classes_with_natives)
701    template = string.Template("""\
702JNI_ZERO_COMPONENT_BUILD_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) {
703${NATIVES}\
704  return true;
705}
706
707""")
708    values = {
709        'REGISTER_NAME':
710        _GetRegistrationFunctionName(self.fully_qualified_class),
711        'NATIVES': natives
712    }
713    self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values))
714
715  def _GetRegisterNativesImplString(self, java_classes_with_natives):
716    """Returns the shared implementation for RegisterNatives."""
717    template = string.Template("""\
718  const int kMethods_${JAVA_CLASS}Size =
719      std::size(${NAMESPACE}kMethods_${JAVA_CLASS});
720  if (env->RegisterNatives(
721      ${JAVA_CLASS_ACCESSOR},
722      ${NAMESPACE}kMethods_${JAVA_CLASS},
723      kMethods_${JAVA_CLASS}Size) < 0) {
724    jni_zero::internal::HandleRegistrationError(env,
725        ${JAVA_CLASS_ACCESSOR},
726        __FILE__);
727    return false;
728  }
729
730""")
731    return self._SubstituteNativeMethods(java_classes_with_natives, template)
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': _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN,
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.jni_obj.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 = _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN
820    else:
821      param_names = _SWITCH_NUM_TO_BE_INERSERTED_LATER_TOKEN + ', ' + 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 = 'int 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