1#!/usr/bin/env python
2# Copyright 2017 The Chromium Authors. All rights reserved.
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 helper for manual JNI registration.
7
8Creates a header file with two static functions: RegisterMainDexNatives() and
9RegisterNonMainDexNatives(). Together, these will use manual JNI registration
10to register all native methods that exist within an application."""
11
12import argparse
13import functools
14import multiprocessing
15import os
16import string
17import sys
18import zipfile
19
20import jni_generator
21from util import build_utils
22
23# All but FULL_CLASS_NAME, which is used only for sorting.
24MERGEABLE_KEYS = [
25    'CLASS_PATH_DECLARATIONS',
26    'FORWARD_DECLARATIONS',
27    'JNI_NATIVE_METHOD',
28    'JNI_NATIVE_METHOD_ARRAY',
29    'PROXY_NATIVE_SIGNATURES',
30    'PROXY_NATIVE_METHOD_ARRAY',
31    'PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX',
32    'REGISTER_MAIN_DEX_NATIVES',
33    'REGISTER_NON_MAIN_DEX_NATIVES',
34]
35
36
37def _Generate(java_file_paths,
38              srcjar_path,
39              proxy_opts,
40              header_path=None,
41              namespace=''):
42  """Generates files required to perform JNI registration.
43
44  Generates a srcjar containing a single class, GEN_JNI, that contains all
45  native method declarations.
46
47  Optionally generates a header file that provides functions
48  (RegisterMainDexNatives and RegisterNonMainDexNatives) to perform
49  JNI registration.
50
51  Args:
52    java_file_paths: A list of java file paths.
53    srcjar_path: Path to the GEN_JNI srcjar.
54    header_path: If specified, generates a header file in this location.
55    namespace: If specified, sets the namespace for the generated header file.
56  """
57  # Without multiprocessing, script takes ~13 seconds for chrome_public_apk
58  # on a z620. With multiprocessing, takes ~2 seconds.
59  pool = multiprocessing.Pool()
60
61  results = []
62  for d in pool.imap_unordered(
63      functools.partial(_DictForPath, use_proxy_hash=proxy_opts.use_hash),
64      java_file_paths):
65    if d:
66      results.append(d)
67  pool.close()
68
69  # Sort to make output deterministic.
70  results.sort(key=lambda d: d['FULL_CLASS_NAME'])
71
72  combined_dict = {}
73  for key in MERGEABLE_KEYS:
74    combined_dict[key] = ''.join(d.get(key, '') for d in results)
75
76  if header_path:
77    combined_dict['HEADER_GUARD'] = \
78        os.path.splitext(header_path)[0].replace('/', '_').upper() + '_'
79    combined_dict['NAMESPACE'] = namespace
80    header_content = CreateFromDict(combined_dict, proxy_opts.use_hash)
81    with build_utils.AtomicOutput(header_path, mode='w') as f:
82      f.write(header_content)
83
84  with build_utils.AtomicOutput(srcjar_path) as f:
85    with zipfile.ZipFile(f, 'w') as srcjar:
86      build_utils.AddToZipHermetic(
87          srcjar,
88          '%s.java' % jni_generator.ProxyHelpers.GetQualifiedClass(
89              proxy_opts.use_hash),
90          data=CreateProxyJavaFromDict(combined_dict, proxy_opts))
91
92
93def _DictForPath(path, use_proxy_hash=False):
94  with open(path) as f:
95    contents = jni_generator.RemoveComments(f.read())
96    if '@JniIgnoreNatives' in contents:
97      return None
98
99  fully_qualified_class = jni_generator.ExtractFullyQualifiedJavaClassName(
100      path, contents)
101  natives = jni_generator.ExtractNatives(contents, 'long')
102
103  natives += jni_generator.ProxyHelpers.ExtractStaticProxyNatives(
104      fully_qualified_class=fully_qualified_class,
105      contents=contents,
106      ptr_type='long',
107      use_hash=use_proxy_hash)
108  if len(natives) == 0:
109    return None
110  namespace = jni_generator.ExtractJNINamespace(contents)
111  jni_params = jni_generator.JniParams(fully_qualified_class)
112  jni_params.ExtractImportsAndInnerClasses(contents)
113  is_main_dex = jni_generator.IsMainDexJavaClass(contents)
114  header_generator = HeaderGenerator(namespace, fully_qualified_class, natives,
115                                     jni_params, is_main_dex, use_proxy_hash)
116  return header_generator.Generate()
117
118
119def _SetProxyRegistrationFields(registration_dict, use_hash):
120  registration_template = string.Template("""\
121
122static const JNINativeMethod kMethods_${ESCAPED_PROXY_CLASS}[] = {
123${KMETHODS}
124};
125
126namespace {
127
128JNI_REGISTRATION_EXPORT bool ${REGISTRATION_NAME}(JNIEnv* env) {
129  const int number_of_methods = base::size(kMethods_${ESCAPED_PROXY_CLASS});
130
131  base::android::ScopedJavaLocalRef<jclass> native_clazz =
132      base::android::GetClass(env, "${PROXY_CLASS}");
133  if (env->RegisterNatives(
134      native_clazz.obj(),
135      kMethods_${ESCAPED_PROXY_CLASS},
136      number_of_methods) < 0) {
137
138    jni_generator::HandleRegistrationError(env, native_clazz.obj(), __FILE__);
139    return false;
140  }
141
142  return true;
143}
144
145}  // namespace
146""")
147
148  registration_call = string.Template("""\
149
150  // Register natives in a proxy.
151  if (!${REGISTRATION_NAME}(env)) {
152    return false;
153  }
154""")
155
156  sub_dict = {
157      'ESCAPED_PROXY_CLASS':
158      jni_generator.EscapeClassName(
159          jni_generator.ProxyHelpers.GetQualifiedClass(use_hash)),
160      'PROXY_CLASS':
161      jni_generator.ProxyHelpers.GetQualifiedClass(use_hash),
162      'KMETHODS':
163      registration_dict['PROXY_NATIVE_METHOD_ARRAY'],
164      'REGISTRATION_NAME':
165      jni_generator.GetRegistrationFunctionName(
166          jni_generator.ProxyHelpers.GetQualifiedClass(use_hash)),
167  }
168
169  if registration_dict['PROXY_NATIVE_METHOD_ARRAY']:
170    proxy_native_array = registration_template.substitute(sub_dict)
171    proxy_natives_registration = registration_call.substitute(sub_dict)
172  else:
173    proxy_native_array = ''
174    proxy_natives_registration = ''
175
176  if registration_dict['PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX']:
177    sub_dict['REGISTRATION_NAME'] += 'MAIN_DEX'
178    sub_dict['ESCAPED_PROXY_CLASS'] += 'MAIN_DEX'
179    sub_dict['KMETHODS'] = (
180        registration_dict['PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX'])
181    proxy_native_array += registration_template.substitute(sub_dict)
182    main_dex_call = registration_call.substitute(sub_dict)
183  else:
184    main_dex_call = ''
185
186  registration_dict['PROXY_NATIVE_METHOD_ARRAY'] = proxy_native_array
187  registration_dict['REGISTER_PROXY_NATIVES'] = proxy_natives_registration
188  registration_dict['REGISTER_MAIN_DEX_PROXY_NATIVES'] = main_dex_call
189
190
191def CreateProxyJavaFromDict(registration_dict, proxy_opts):
192  template = string.Template("""\
193// Copyright 2018 The Chromium Authors. All rights reserved.
194// Use of this source code is governed by a BSD-style license that can be
195// found in the LICENSE file.
196
197package ${PACKAGE};
198
199// This file is autogenerated by
200//     base/android/jni_generator/jni_registration_generator.py
201// Please do not change its content.
202
203public class ${CLASS_NAME} {
204  public static final boolean TESTING_ENABLED = ${TESTING_ENABLED};
205  public static final boolean REQUIRE_MOCK = ${REQUIRE_MOCK};
206${SIGNATURES}
207
208}
209""")
210
211  return template.substitute({
212      'TESTING_ENABLED':
213      str(proxy_opts.enable_mocks).lower(),
214      'REQUIRE_MOCK':
215      str(proxy_opts.require_mocks).lower(),
216      'CLASS_NAME':
217      jni_generator.ProxyHelpers.GetClass(proxy_opts.use_hash),
218      'PACKAGE':
219      jni_generator.ProxyHelpers.GetPackage(proxy_opts.use_hash).replace(
220          '/', '.'),
221      'SIGNATURES':
222      registration_dict['PROXY_NATIVE_SIGNATURES']
223  })
224
225
226def CreateFromDict(registration_dict, use_hash):
227  """Returns the content of the header file."""
228
229  template = string.Template("""\
230// Copyright 2017 The Chromium Authors. All rights reserved.
231// Use of this source code is governed by a BSD-style license that can be
232// found in the LICENSE file.
233
234
235// This file is autogenerated by
236//     base/android/jni_generator/jni_registration_generator.py
237// Please do not change its content.
238
239#ifndef ${HEADER_GUARD}
240#define ${HEADER_GUARD}
241
242#include <jni.h>
243
244#include "base/android/jni_generator/jni_generator_helper.h"
245#include "base/android/jni_int_wrapper.h"
246#include "base/stl_util.h"  // For base::size().
247
248
249// Step 1: Forward declarations (classes).
250${CLASS_PATH_DECLARATIONS}
251
252// Step 2: Forward declarations (methods).
253
254${FORWARD_DECLARATIONS}
255
256// Step 3: Method declarations.
257
258${JNI_NATIVE_METHOD_ARRAY}\
259${PROXY_NATIVE_METHOD_ARRAY}\
260
261${JNI_NATIVE_METHOD}
262// Step 4: Main dex and non-main dex registration functions.
263
264namespace ${NAMESPACE} {
265
266bool RegisterMainDexNatives(JNIEnv* env) {\
267${REGISTER_MAIN_DEX_PROXY_NATIVES}
268${REGISTER_MAIN_DEX_NATIVES}
269  return true;
270}
271
272bool RegisterNonMainDexNatives(JNIEnv* env) {\
273${REGISTER_PROXY_NATIVES}
274${REGISTER_NON_MAIN_DEX_NATIVES}
275  return true;
276}
277
278}  // namespace ${NAMESPACE}
279
280#endif  // ${HEADER_GUARD}
281""")
282  _SetProxyRegistrationFields(registration_dict, use_hash)
283
284  if len(registration_dict['FORWARD_DECLARATIONS']) == 0:
285    return ''
286
287  return template.substitute(registration_dict)
288
289
290class HeaderGenerator(object):
291  """Generates an inline header file for JNI registration."""
292
293  def __init__(self, namespace, fully_qualified_class, natives, jni_params,
294               main_dex, use_proxy_hash):
295    self.namespace = namespace
296    self.natives = natives
297    self.proxy_natives = [n for n in natives if n.is_proxy]
298    self.non_proxy_natives = [n for n in natives if not n.is_proxy]
299    self.fully_qualified_class = fully_qualified_class
300    self.jni_params = jni_params
301    self.class_name = self.fully_qualified_class.split('/')[-1]
302    self.main_dex = main_dex
303    self.helper = jni_generator.HeaderFileGeneratorHelper(
304        self.class_name, fully_qualified_class, use_proxy_hash)
305    self.use_proxy_hash = use_proxy_hash
306    self.registration_dict = None
307
308  def Generate(self):
309    self.registration_dict = {'FULL_CLASS_NAME': self.fully_qualified_class}
310    self._AddClassPathDeclarations()
311    self._AddForwardDeclaration()
312    self._AddJNINativeMethodsArrays()
313    self._AddProxySignatures()
314    self._AddProxyNativeMethodKStrings()
315    self._AddRegisterNativesCalls()
316    self._AddRegisterNativesFunctions()
317    return self.registration_dict
318
319  def _SetDictValue(self, key, value):
320    self.registration_dict[key] = jni_generator.WrapOutput(value)
321
322  def _AddClassPathDeclarations(self):
323    classes = self.helper.GetUniqueClasses(self.natives)
324    self._SetDictValue(
325        'CLASS_PATH_DECLARATIONS',
326        self.helper.GetClassPathLines(classes, declare_only=True))
327
328  def _AddForwardDeclaration(self):
329    """Add the content of the forward declaration to the dictionary."""
330    template = string.Template("""\
331JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(
332    JNIEnv* env,
333    ${PARAMS_IN_STUB});
334""")
335    forward_declaration = ''
336    for native in self.natives:
337      value = {
338          'RETURN': jni_generator.JavaDataTypeToC(native.return_type),
339          'STUB_NAME': self.helper.GetStubName(native),
340          'PARAMS_IN_STUB': jni_generator.GetParamsInStub(native),
341      }
342      forward_declaration += template.substitute(value)
343    self._SetDictValue('FORWARD_DECLARATIONS', forward_declaration)
344
345  def _AddRegisterNativesCalls(self):
346    """Add the body of the RegisterNativesImpl method to the dictionary."""
347
348    # Only register if there is at least 1 non-proxy native
349    if len(self.non_proxy_natives) == 0:
350      return ''
351
352    template = string.Template("""\
353  if (!${REGISTER_NAME}(env))
354    return false;
355""")
356    value = {
357        'REGISTER_NAME':
358        jni_generator.GetRegistrationFunctionName(self.fully_qualified_class)
359    }
360    register_body = template.substitute(value)
361    if self.main_dex:
362      self._SetDictValue('REGISTER_MAIN_DEX_NATIVES', register_body)
363    else:
364      self._SetDictValue('REGISTER_NON_MAIN_DEX_NATIVES', register_body)
365
366  def _AddJNINativeMethodsArrays(self):
367    """Returns the implementation of the array of native methods."""
368    template = string.Template("""\
369static const JNINativeMethod kMethods_${JAVA_CLASS}[] = {
370${KMETHODS}
371};
372
373""")
374    open_namespace = ''
375    close_namespace = ''
376    if self.namespace:
377      parts = self.namespace.split('::')
378      all_namespaces = ['namespace %s {' % ns for ns in parts]
379      open_namespace = '\n'.join(all_namespaces) + '\n'
380      all_namespaces = ['}  // namespace %s' % ns for ns in parts]
381      all_namespaces.reverse()
382      close_namespace = '\n'.join(all_namespaces) + '\n\n'
383
384    body = self._SubstituteNativeMethods(template)
385    self._SetDictValue('JNI_NATIVE_METHOD_ARRAY', ''.join((open_namespace, body,
386                                                           close_namespace)))
387
388  def _GetKMethodsString(self, clazz):
389    ret = []
390    for native in self.non_proxy_natives:
391      if (native.java_class_name == clazz
392          or (not native.java_class_name and clazz == self.class_name)):
393        ret += [self._GetKMethodArrayEntry(native)]
394    return '\n'.join(ret)
395
396  def _GetKMethodArrayEntry(self, native):
397    template = string.Template('    { "${NAME}", ${JNI_SIGNATURE}, ' +
398                               'reinterpret_cast<void*>(${STUB_NAME}) },')
399
400    name = 'native' + native.name
401    if native.is_proxy:
402      # Literal name of the native method in the class that contains the actual
403      # native declaration.
404      name = native.proxy_name
405    values = {
406        'NAME':
407        name,
408        'JNI_SIGNATURE':
409        self.jni_params.Signature(native.params, native.return_type),
410        'STUB_NAME':
411        self.helper.GetStubName(native)
412    }
413    return template.substitute(values)
414
415  def _AddProxySignatures(self):
416    self.registration_dict['PROXY_NATIVE_SIGNATURES'] = ('\n'.join(
417        _MakeProxySignature(n) for n in self.proxy_natives))
418
419  def _AddProxyNativeMethodKStrings(self):
420    """Returns KMethodString for wrapped native methods in all_classes """
421
422    if self.main_dex:
423      key = 'PROXY_NATIVE_METHOD_ARRAY_MAIN_DEX'
424    else:
425      key = 'PROXY_NATIVE_METHOD_ARRAY'
426
427    proxy_k_strings = ('\n'.join(
428        self._GetKMethodArrayEntry(p) for p in self.proxy_natives))
429
430    self._SetDictValue(key, proxy_k_strings)
431
432  def _SubstituteNativeMethods(self, template, sub_proxy=False):
433    """Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided
434    template."""
435    ret = []
436    all_classes = self.helper.GetUniqueClasses(self.natives)
437    all_classes[self.class_name] = self.fully_qualified_class
438
439    for clazz, full_clazz in all_classes.items():
440      if not sub_proxy:
441        if clazz == jni_generator.ProxyHelpers.GetClass(self.use_proxy_hash):
442          continue
443
444      kmethods = self._GetKMethodsString(clazz)
445      namespace_str = ''
446      if self.namespace:
447        namespace_str = self.namespace + '::'
448      if kmethods:
449        values = {
450            'NAMESPACE': namespace_str,
451            'JAVA_CLASS': jni_generator.EscapeClassName(full_clazz),
452            'KMETHODS': kmethods
453        }
454        ret += [template.substitute(values)]
455    if not ret: return ''
456    return '\n'.join(ret)
457
458  def GetJNINativeMethodsString(self):
459    """Returns the implementation of the array of native methods."""
460    template = string.Template("""\
461static const JNINativeMethod kMethods_${JAVA_CLASS}[] = {
462${KMETHODS}
463
464};
465""")
466    return self._SubstituteNativeMethods(template)
467
468  def _AddRegisterNativesFunctions(self):
469    """Returns the code for RegisterNatives."""
470    natives = self._GetRegisterNativesImplString()
471    if not natives:
472      return ''
473    template = string.Template("""\
474JNI_REGISTRATION_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) {
475${NATIVES}\
476  return true;
477}
478
479""")
480    values = {
481        'REGISTER_NAME':
482        jni_generator.GetRegistrationFunctionName(self.fully_qualified_class),
483        'NATIVES':
484        natives
485    }
486    self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values))
487
488  def _GetRegisterNativesImplString(self):
489    """Returns the shared implementation for RegisterNatives."""
490    template = string.Template("""\
491  const int kMethods_${JAVA_CLASS}Size =
492      base::size(${NAMESPACE}kMethods_${JAVA_CLASS});
493  if (env->RegisterNatives(
494      ${JAVA_CLASS}_clazz(env),
495      ${NAMESPACE}kMethods_${JAVA_CLASS},
496      kMethods_${JAVA_CLASS}Size) < 0) {
497    jni_generator::HandleRegistrationError(env,
498        ${JAVA_CLASS}_clazz(env),
499        __FILE__);
500    return false;
501  }
502
503""")
504    # Only register if there is a native method not in a proxy,
505    # since all the proxies will be registered together.
506    if len(self.non_proxy_natives) != 0:
507      return self._SubstituteNativeMethods(template)
508    return ''
509
510
511def _MakeProxySignature(proxy_native):
512  signature_template = string.Template("""
513  public static native ${RETURN_TYPE} ${NAME}(${PARAMS});""")
514
515  return signature_template.substitute({
516      'RETURN_TYPE':
517      proxy_native.return_type,
518      'NAME':
519      proxy_native.proxy_name,
520      'PARAMS':
521      jni_generator.JniParams.MakeProxyParamSignature(proxy_native.params)
522  })
523
524
525class ProxyOptions:
526
527  def __init__(self, **kwargs):
528    self.use_hash = kwargs.get('use_hash', False)
529    self.enable_mocks = kwargs.get('enable_mocks', False)
530    self.require_mocks = kwargs.get('require_mocks', False)
531    # Can never require and disable.
532    assert self.enable_mocks or not self.require_mocks
533
534
535def main(argv):
536  arg_parser = argparse.ArgumentParser()
537  build_utils.AddDepfileOption(arg_parser)
538
539  arg_parser.add_argument(
540      '--sources-files',
541      required=True,
542      action='append',
543      help='A list of .sources files which contain Java '
544      'file paths.')
545  arg_parser.add_argument(
546      '--header-path', help='Path to output header file (optional).')
547  arg_parser.add_argument(
548      '--srcjar-path',
549      required=True,
550      help='Path to output srcjar for GEN_JNI.java (Or J/N.java if proxy'
551      ' hash is enabled).')
552  arg_parser.add_argument(
553      '--sources-exclusions',
554      default=[],
555      help='A list of Java files which should be ignored '
556      'by the parser.')
557  arg_parser.add_argument(
558      '--namespace',
559      default='',
560      help='Namespace to wrap the registration functions '
561      'into.')
562  # TODO(crbug.com/898261) hook these flags up to the build config to enable
563  # mocking in instrumentation tests
564  arg_parser.add_argument(
565      '--enable_proxy_mocks',
566      default=False,
567      action='store_true',
568      help='Allows proxy native impls to be mocked through Java.')
569  arg_parser.add_argument(
570      '--require_mocks',
571      default=False,
572      action='store_true',
573      help='Requires all used native implementations to have a mock set when '
574      'called. Otherwise an exception will be thrown.')
575  arg_parser.add_argument(
576      '--use_proxy_hash',
577      action='store_true',
578      help='Enables hashing of the native declaration for methods in '
579      'an @JniNatives interface')
580  args = arg_parser.parse_args(build_utils.ExpandFileArgs(argv[1:]))
581
582  if not args.enable_proxy_mocks and args.require_mocks:
583    arg_parser.error(
584        'Invalid arguments: --require_mocks without --enable_proxy_mocks. '
585        'Cannot require mocks if they are not enabled.')
586
587  sources_files = sorted(set(build_utils.ParseGnList(args.sources_files)))
588  proxy_opts = ProxyOptions(
589      use_hash=args.use_proxy_hash,
590      require_mocks=args.require_mocks,
591      enable_mocks=args.enable_proxy_mocks)
592
593  java_file_paths = []
594  for f in sources_files:
595    # Skip generated files, since the GN targets do not declare any deps.
596    java_file_paths.extend(
597        p for p in build_utils.ReadSourcesList(f)
598        if p.startswith('..') and p not in args.sources_exclusions)
599  _Generate(
600      java_file_paths,
601      args.srcjar_path,
602      proxy_opts=proxy_opts,
603      header_path=args.header_path,
604      namespace=args.namespace)
605
606  if args.depfile:
607    build_utils.WriteDepfile(
608        args.depfile,
609        args.srcjar_path,
610        sources_files + java_file_paths,
611        add_pydeps=False)
612
613
614if __name__ == '__main__':
615  sys.exit(main(sys.argv))
616