1 // Copyright 2023 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
5 #include "third_party/jni_zero/jni_zero.h"
6
7 #include <sys/prctl.h>
8
9 #include "third_party/jni_zero/generate_jni/JniInit_jni.h"
10 #include "third_party/jni_zero/jni_methods.h"
11 #include "third_party/jni_zero/jni_zero_internal.h"
12 #include "third_party/jni_zero/logging.h"
13
14 #if defined(JNI_ZERO_MULTIPLEXING_ENABLED)
15 extern const int64_t kJniZeroHashWhole;
16 extern const int64_t kJniZeroHashPriority;
17 #endif
18 namespace jni_zero {
19 namespace {
20 // Until we fully migrate base's jni_android, we will maintain a copy of this
21 // global here and will have base set this variable when it sets its own.
22 JavaVM* g_jvm = nullptr;
23
24 jclass (*g_class_resolver)(JNIEnv*, const char*, const char*) = nullptr;
25
26 void (*g_exception_handler_callback)(JNIEnv*) = nullptr;
27
GetClassInternal(JNIEnv * env,const char * class_name,const char * split_name)28 jclass GetClassInternal(JNIEnv* env,
29 const char* class_name,
30 const char* split_name) {
31 jclass clazz;
32 if (g_class_resolver != nullptr) {
33 clazz = g_class_resolver(env, class_name, split_name);
34 } else {
35 // Our generated code uses dots instead of slashes for ease of use with
36 // ClassLoader.loadCLass, so convert this.
37 size_t bufsize = strlen(class_name) + 1;
38 char slash_name[bufsize];
39 memmove(slash_name, class_name, bufsize);
40 for (size_t i = 0; i < bufsize; ++i) {
41 if (slash_name[i] == '.') {
42 slash_name[i] = '/';
43 }
44 }
45 clazz = env->FindClass(slash_name);
46 }
47 if (ClearException(env) || !clazz) {
48 JNI_ZERO_FLOG("Failed to find class %s", class_name);
49 }
50 return clazz;
51 }
52
LazyGetClassInternal(JNIEnv * env,const char * class_name,const char * split_name,std::atomic<jclass> * atomic_class_id)53 jclass LazyGetClassInternal(JNIEnv* env,
54 const char* class_name,
55 const char* split_name,
56 std::atomic<jclass>* atomic_class_id) {
57 jclass ret = nullptr;
58 ScopedJavaGlobalRef<jclass> clazz(
59 env, GetClassInternal(env, class_name, split_name));
60 if (atomic_class_id->compare_exchange_strong(ret, clazz.obj(),
61 std::memory_order_acq_rel)) {
62 // We intentionally leak the global ref since we are now storing it as a raw
63 // pointer in |atomic_class_id|.
64 ret = clazz.Release();
65 }
66 return ret;
67 }
68
GetSystemClassGlobalRef(JNIEnv * env,const char * class_name)69 jclass GetSystemClassGlobalRef(JNIEnv* env, const char* class_name) {
70 return static_cast<jclass>(env->NewGlobalRef(env->FindClass(class_name)));
71 }
72
73 } // namespace
74
75 jclass g_object_class = nullptr;
76 jclass g_string_class = nullptr;
77
AttachCurrentThread()78 JNIEnv* AttachCurrentThread() {
79 JNI_ZERO_DCHECK(g_jvm);
80 JNIEnv* env = nullptr;
81 jint ret = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2);
82 if (ret == JNI_EDETACHED || !env) {
83 JavaVMAttachArgs args;
84 args.version = JNI_VERSION_1_2;
85 args.group = nullptr;
86
87 // 16 is the maximum size for thread names on Android.
88 char thread_name[16];
89 int err = prctl(PR_GET_NAME, thread_name);
90 if (err < 0) {
91 JNI_ZERO_ELOG("prctl(PR_GET_NAME)");
92 args.name = nullptr;
93 } else {
94 args.name = thread_name;
95 }
96
97 #if defined(JNI_ZERO_IS_ROBOLECTRIC)
98 ret = g_jvm->AttachCurrentThread(reinterpret_cast<void**>(&env), &args);
99 #else
100 ret = g_jvm->AttachCurrentThread(&env, &args);
101 #endif
102 JNI_ZERO_CHECK(ret == JNI_OK);
103 }
104 return env;
105 }
106
AttachCurrentThreadWithName(const std::string & thread_name)107 JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name) {
108 JNI_ZERO_DCHECK(g_jvm);
109 JavaVMAttachArgs args;
110 args.version = JNI_VERSION_1_2;
111 args.name = const_cast<char*>(thread_name.c_str());
112 args.group = nullptr;
113 JNIEnv* env = nullptr;
114 #if defined(JNI_ZERO_IS_ROBOLECTRIC)
115 jint ret = g_jvm->AttachCurrentThread(reinterpret_cast<void**>(&env), &args);
116 #else
117 jint ret = g_jvm->AttachCurrentThread(&env, &args);
118 #endif
119 JNI_ZERO_CHECK(ret == JNI_OK);
120 return env;
121 }
122
DetachFromVM()123 void DetachFromVM() {
124 // Ignore the return value, if the thread is not attached, DetachCurrentThread
125 // will fail. But it is ok as the native thread may never be attached.
126 if (g_jvm) {
127 g_jvm->DetachCurrentThread();
128 }
129 }
130
InitVM(JavaVM * vm)131 void InitVM(JavaVM* vm) {
132 g_jvm = vm;
133 JNIEnv* env = AttachCurrentThread();
134 g_object_class = GetSystemClassGlobalRef(env, "java/lang/Object");
135 g_string_class = GetSystemClassGlobalRef(env, "java/lang/String");
136 #if defined(JNI_ZERO_MULTIPLEXING_ENABLED)
137 Java_JniInit_crashIfMultiplexingMisaligned(env, kJniZeroHashWhole,
138 kJniZeroHashPriority);
139 #else
140 // Mark as used when multiplexing not enabled.
141 (void)&Java_JniInit_crashIfMultiplexingMisaligned;
142 #endif
143 CheckException(env);
144 }
145
DisableJvmForTesting()146 void DisableJvmForTesting() {
147 g_jvm = nullptr;
148 }
149
IsVMInitialized()150 bool IsVMInitialized() {
151 return g_jvm != nullptr;
152 }
153
GetVM()154 JavaVM* GetVM() {
155 return g_jvm;
156 }
157
HasException(JNIEnv * env)158 bool HasException(JNIEnv* env) {
159 return env->ExceptionCheck() != JNI_FALSE;
160 }
161
ClearException(JNIEnv * env)162 bool ClearException(JNIEnv* env) {
163 if (!HasException(env)) {
164 return false;
165 }
166 env->ExceptionDescribe();
167 env->ExceptionClear();
168 return true;
169 }
170
SetExceptionHandler(void (* callback)(JNIEnv *))171 void SetExceptionHandler(void (*callback)(JNIEnv*)) {
172 g_exception_handler_callback = callback;
173 }
174
CheckException(JNIEnv * env)175 void CheckException(JNIEnv* env) {
176 if (!HasException(env)) {
177 return;
178 }
179
180 if (g_exception_handler_callback) {
181 return g_exception_handler_callback(env);
182 }
183 JNI_ZERO_FLOG("jni_zero crashing due to uncaught Java exception");
184 }
185
SetClassResolver(jclass (* resolver)(JNIEnv *,const char *,const char *))186 void SetClassResolver(jclass (*resolver)(JNIEnv*, const char*, const char*)) {
187 g_class_resolver = resolver;
188 }
189
GetClass(JNIEnv * env,const char * class_name,const char * split_name)190 ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env,
191 const char* class_name,
192 const char* split_name) {
193 return ScopedJavaLocalRef<jclass>(
194 env, GetClassInternal(env, class_name, split_name));
195 }
196
GetClass(JNIEnv * env,const char * class_name)197 ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) {
198 return ScopedJavaLocalRef<jclass>(env, GetClassInternal(env, class_name, ""));
199 }
200
201 template <MethodID::Type type>
Get(JNIEnv * env,jclass clazz,const char * method_name,const char * jni_signature)202 jmethodID MethodID::Get(JNIEnv* env,
203 jclass clazz,
204 const char* method_name,
205 const char* jni_signature) {
206 auto get_method_ptr = type == MethodID::TYPE_STATIC
207 ? &JNIEnv::GetStaticMethodID
208 : &JNIEnv::GetMethodID;
209 jmethodID id = (env->*get_method_ptr)(clazz, method_name, jni_signature);
210 if (ClearException(env) || !id) {
211 JNI_ZERO_FLOG("Failed to find class %smethod %s %s",
212 (type == TYPE_STATIC ? "static " : ""), method_name,
213 jni_signature);
214 }
215 return id;
216 }
217
218 // If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call
219 // into ::Get() above. If there's a race, it's ok since the values are the same
220 // (and the duplicated effort will happen only once).
221 template <MethodID::Type type>
LazyGet(JNIEnv * env,jclass clazz,const char * method_name,const char * jni_signature,std::atomic<jmethodID> * atomic_method_id)222 jmethodID MethodID::LazyGet(JNIEnv* env,
223 jclass clazz,
224 const char* method_name,
225 const char* jni_signature,
226 std::atomic<jmethodID>* atomic_method_id) {
227 const jmethodID value = atomic_method_id->load(std::memory_order_acquire);
228 if (value) {
229 return value;
230 }
231 jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature);
232 atomic_method_id->store(id, std::memory_order_release);
233 return id;
234 }
235
236 // Various template instantiations.
237 template jmethodID MethodID::Get<MethodID::TYPE_STATIC>(
238 JNIEnv* env,
239 jclass clazz,
240 const char* method_name,
241 const char* jni_signature);
242
243 template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>(
244 JNIEnv* env,
245 jclass clazz,
246 const char* method_name,
247 const char* jni_signature);
248
249 template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>(
250 JNIEnv* env,
251 jclass clazz,
252 const char* method_name,
253 const char* jni_signature,
254 std::atomic<jmethodID>* atomic_method_id);
255
256 template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>(
257 JNIEnv* env,
258 jclass clazz,
259 const char* method_name,
260 const char* jni_signature,
261 std::atomic<jmethodID>* atomic_method_id);
262
263 namespace internal {
LazyGetClass(JNIEnv * env,const char * class_name,const char * split_name,std::atomic<jclass> * atomic_class_id)264 jclass LazyGetClass(JNIEnv* env,
265 const char* class_name,
266 const char* split_name,
267 std::atomic<jclass>* atomic_class_id) {
268 jclass ret = atomic_class_id->load(std::memory_order_acquire);
269 if (ret == nullptr) {
270 ret = LazyGetClassInternal(env, class_name, split_name, atomic_class_id);
271 }
272 return ret;
273 }
274
LazyGetClass(JNIEnv * env,const char * class_name,std::atomic<jclass> * atomic_class_id)275 jclass LazyGetClass(JNIEnv* env,
276 const char* class_name,
277 std::atomic<jclass>* atomic_class_id) {
278 jclass ret = atomic_class_id->load(std::memory_order_acquire);
279 if (ret == nullptr) {
280 ret = LazyGetClassInternal(env, class_name, "", atomic_class_id);
281 }
282 return ret;
283 }
284
285 } // namespace internal
286 } // namespace jni_zero
287