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