xref: /aosp_15_r20/external/cronet/third_party/jni_zero/jni_zero.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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