xref: /aosp_15_r20/external/fbjni/cxx/fbjni/detail/Environment.cpp (revision 65c59e023c5336bbd4a23be7af78407e3d80e7e7)
1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <fbjni/fbjni.h>
18 
19 #ifdef __ANDROID__
20 #include <sys/prctl.h>
21 #endif // __ANDROID__
22 
23 #include <functional>
24 #include <vector>
25 #ifndef _WIN32
26 #include <pthread.h>
27 #else
28 #include <windows.h>
29 #endif
30 
31 namespace facebook {
32 namespace jni {
33 
34 namespace {
35 
36 JavaVM* g_vm = nullptr;
37 
38 struct EnvironmentInitializer {
EnvironmentInitializerfacebook::jni::__anonc0ffca210111::EnvironmentInitializer39   EnvironmentInitializer(JavaVM* vm) {
40     FBJNI_ASSERT(!g_vm);
41     FBJNI_ASSERT(vm);
42     g_vm = vm;
43   }
44 };
45 
getEnv(JNIEnv ** env)46 int getEnv(JNIEnv** env) {
47   FBJNI_ASSERT(g_vm);
48   // g_vm->GetEnv() might not clear the env* in failure cases.
49   *env = nullptr;
50   jint ret = g_vm->GetEnv((void**)env, JNI_VERSION_1_6);
51   // Other possibilites are that JNI_VERSION_1_6 is invalid, or some
52   // unknown return was received.
53   FBJNI_ASSERT(ret == JNI_OK || ret == JNI_EDETACHED);
54   return ret;
55 }
56 
57 // Some jni.h define the first arg to AttachCurrentThread as void**,
58 // and some as JNIEnv**.  This hack allows both to work.
59 
60 template <typename>
61 struct AttachTraits;
62 
63 template <>
64 struct AttachTraits<jint (JavaVM::*)(JNIEnv**, void*)> {
65   using EnvType = JNIEnv*;
66 };
67 
68 template <>
69 struct AttachTraits<jint (JavaVM::*)(void**, void*)> {
70   using EnvType = void*;
71 };
72 
getThreadName()73 std::string getThreadName() {
74 #ifdef _WIN32
75   return "";
76 #else // _WIN32
77   constexpr int kMaxThreadNameSize = 100;
78   int ret = 0;
79   char threadName[kMaxThreadNameSize];
80 #ifdef __ANDROID__
81   ret = prctl(PR_GET_NAME, threadName);
82 #else
83   ret = pthread_getname_np(pthread_self(), threadName, sizeof(threadName));
84 #endif
85   if (ret != 0) {
86     return "";
87   }
88   return threadName;
89 #endif // _WIN32
90 }
91 
attachCurrentThread()92 JNIEnv* attachCurrentThread() {
93   JavaVMAttachArgs args{JNI_VERSION_1_6, nullptr, nullptr};
94 
95   const auto threadName = getThreadName();
96   std::vector<char> nameBuf;
97   if (threadName.size()) {
98     const char* cName = threadName.c_str();
99     nameBuf.assign(cName, cName + threadName.size() + 1);
100     args.name = nameBuf.data();
101   }
102 
103   using AttachEnvType =
104       typename AttachTraits<decltype(&JavaVM::AttachCurrentThread)>::EnvType;
105   AttachEnvType env;
106   auto result = g_vm->AttachCurrentThread(&env, &args);
107   FBJNI_ASSERT(result == JNI_OK);
108   return reinterpret_cast<JNIEnv*>(env);
109 }
110 
111 } // namespace
112 
113 /* static */
initialize(JavaVM * vm)114 void Environment::initialize(JavaVM* vm) {
115   static EnvironmentInitializer init(vm);
116 }
117 
118 namespace {
119 
120 #ifndef _WIN32
121 typedef pthread_key_t tls_key_t;
122 #else
123 typedef DWORD tls_key_t;
124 #endif
125 
makeKey()126 tls_key_t makeKey() {
127   tls_key_t key;
128 #ifndef _WIN32
129   int ret = pthread_key_create(&key, nullptr);
130   if (ret != 0) {
131     FBJNI_LOGF("pthread_key_create failed: %d", ret);
132   }
133 #else
134   key = TlsAlloc();
135   if (key == TLS_OUT_OF_INDEXES) {
136     FBJNI_LOGF("TlsAlloc failed");
137   }
138 #endif
139   return key;
140 }
141 
getTLKey()142 tls_key_t getTLKey() {
143   static tls_key_t key = makeKey();
144   return key;
145 }
146 
getTLData(tls_key_t key)147 inline detail::TLData* getTLData(tls_key_t key) {
148 #ifndef _WIN32
149   void* raw_data = pthread_getspecific(key);
150 #else
151   LPVOID raw_data = TlsGetValue(key);
152   // TODO: Maybe check for errors here?
153 #endif
154   return reinterpret_cast<detail::TLData*>(raw_data);
155 }
156 
setTLData(tls_key_t key,detail::TLData * data)157 inline void setTLData(tls_key_t key, detail::TLData* data) {
158 #ifndef _WIN32
159   int ret = pthread_setspecific(key, data);
160   if (ret != 0) {
161     (void)ret;
162     FBJNI_LOGF("pthread_setspecific failed: %d", ret);
163   }
164 #else
165   BOOL ret = TlsSetValue(key, data);
166   if (!ret) {
167     FBJNI_LOGF("TlsSetValue failed: %d", GetLastError());
168   }
169 #endif
170 }
171 
172 // This returns non-nullptr iff the env was cached from java.  So it
173 // can return nullptr for a thread which has been registered.
cachedOrNull()174 inline JNIEnv* cachedOrNull() {
175   detail::TLData* pdata = getTLData(getTLKey());
176   return (pdata ? pdata->env : nullptr);
177 }
178 
179 } // namespace
180 
181 namespace detail {
182 
cachedWithAttachmentState(bool & isAttaching)183 JNIEnv* cachedWithAttachmentState(bool& isAttaching) {
184   isAttaching = false;
185   detail::TLData* pdata = getTLData(getTLKey());
186   if (!pdata) {
187     return nullptr;
188   }
189   if (!pdata->env && !pdata->attached) {
190     isAttaching = true;
191   }
192   return pdata->env;
193 }
194 
195 // This will return a cached env if there is one, or get one from JNI
196 // if the thread has already been attached some other way.  If it
197 // returns nullptr, then the thread has never been registered, or the
198 // VM has never been set up for fbjni.
199 
currentOrNull()200 JNIEnv* currentOrNull() {
201   if (!g_vm) {
202     return nullptr;
203   }
204 
205   detail::TLData* pdata = getTLData(getTLKey());
206   if (pdata && pdata->env) {
207     return pdata->env;
208   }
209 
210   JNIEnv* env;
211   if (getEnv(&env) != JNI_OK) {
212     // If there's a ThreadScope on the stack, we should have gotten a
213     // JNIEnv and not ended up here.
214     FBJNI_ASSERT(!pdata || !pdata->attached);
215   }
216   return env;
217 }
218 
219 // To understand JniEnvCacher and ThreadScope, it is helpful to
220 // realize that if a flagged JniEnvCacher is on the stack, then a
221 // flagged ThreadScope cannot be after it.  If a flagged ThreadCacher
222 // is on the stack, then a JniEnvCacher *can* be after it.  So,
223 // ThreadScope's setup and teardown can both assume they are the
224 // first/last interesting objects, but this is not true of
225 // JniEnvCacher.
226 
JniEnvCacher(JNIEnv * env)227 JniEnvCacher::JniEnvCacher(JNIEnv* env) : thisCached_(false) {
228   FBJNI_ASSERT(env);
229 
230   tls_key_t key = getTLKey();
231   detail::TLData* pdata = getTLData(key);
232   if (pdata && pdata->env) {
233     return;
234   }
235 
236   if (!pdata) {
237     pdata = &data_;
238     setTLData(key, pdata);
239     pdata->attached = false;
240   } else {
241     FBJNI_ASSERT(!pdata->env);
242   }
243 
244   pdata->env = env;
245 
246   thisCached_ = true;
247 }
248 
~JniEnvCacher()249 JniEnvCacher::~JniEnvCacher() {
250   if (!thisCached_) {
251     return;
252   }
253 
254   tls_key_t key = getTLKey();
255   TLData* pdata = getTLData(key);
256   FBJNI_ASSERT(pdata);
257   FBJNI_ASSERT(pdata->env != nullptr);
258   pdata->env = nullptr;
259   if (!pdata->attached) {
260     setTLData(key, nullptr);
261   }
262 }
263 
264 } // namespace detail
265 
ThreadScope()266 ThreadScope::ThreadScope() : thisAttached_(false) {
267   if (g_vm == nullptr) {
268     throw std::runtime_error(
269         "fbjni is uninitialized; no thread can be attached.");
270   }
271 
272   JNIEnv* env;
273 
274   // Check if the thread is attached somehow.
275   auto result = getEnv(&env);
276   if (result == JNI_OK) {
277     return;
278   }
279 
280   // At this point, it appears there's no thread attached and no env is
281   // cached, or we would have returned already.  So there better not
282   // be TLData.
283 
284   tls_key_t key = getTLKey();
285   detail::TLData* pdata = getTLData(key);
286   FBJNI_ASSERT(pdata == nullptr);
287   setTLData(key, &data_);
288 
289   data_.env = nullptr;
290   data_.attached = false;
291 
292   attachCurrentThread();
293 
294   data_.attached = true;
295 
296   thisAttached_ = true;
297 }
298 
~ThreadScope()299 ThreadScope::~ThreadScope() {
300   if (!thisAttached_) {
301     return;
302   }
303 
304   tls_key_t key = getTLKey();
305   detail::TLData* pdata = getTLData(key);
306   FBJNI_ASSERT(pdata);
307   FBJNI_ASSERT(pdata->env == nullptr);
308   FBJNI_ASSERT(pdata->attached);
309   FBJNI_ASSERT(g_vm);
310   g_vm->DetachCurrentThread();
311   setTLData(key, nullptr);
312 }
313 
314 /* static */
current()315 JNIEnv* Environment::current() {
316   FBJNI_ASSERT(g_vm);
317   JNIEnv* env = detail::currentOrNull();
318   if (env == nullptr) {
319     throw std::runtime_error(
320         "Unable to retrieve jni environment. Is the thread attached?");
321   }
322   return env;
323 }
324 
325 /* static */
ensureCurrentThreadIsAttached()326 JNIEnv* Environment::ensureCurrentThreadIsAttached() {
327   FBJNI_ASSERT(g_vm);
328   JNIEnv* env = detail::currentOrNull();
329   if (env == nullptr) {
330     env = attachCurrentThread();
331     FBJNI_ASSERT(env);
332   }
333   return env;
334 }
335 
336 /* static */
isGlobalJvmAvailable()337 bool Environment::isGlobalJvmAvailable() {
338   return g_vm != nullptr;
339 }
340 
341 namespace {
342 struct JThreadScopeSupport : JavaClass<JThreadScopeSupport> {
343   static auto constexpr kJavaDescriptor =
344       "Lcom/facebook/jni/ThreadScopeSupport;";
345 
346   // These reinterpret_casts are a totally dangerous pattern. Don't use them.
347   // Use HybridData instead.
runStdFunctionfacebook::jni::__anonc0ffca210311::JThreadScopeSupport348   static void runStdFunction(std::function<void()>&& func) {
349     static const auto method =
350         javaClassStatic()->getStaticMethod<void(jlong)>("runStdFunction");
351     method(javaClassStatic(), reinterpret_cast<jlong>(&func));
352   }
353 
runStdFunctionImplfacebook::jni::__anonc0ffca210311::JThreadScopeSupport354   static void runStdFunctionImpl(alias_ref<JClass>, jlong ptr) {
355     (*reinterpret_cast<std::function<void()>*>(ptr))();
356   }
357 
OnLoadfacebook::jni::__anonc0ffca210311::JThreadScopeSupport358   static void OnLoad() {
359     // We need the javaClassStatic so that the class lookup is cached and that
360     // runStdFunction can be called from a ThreadScope-attached thread.
361     javaClassStatic()->registerNatives({
362         makeNativeMethod("runStdFunctionImpl", runStdFunctionImpl),
363     });
364   }
365 };
366 } // namespace
367 
368 /* static */
OnLoad()369 void ThreadScope::OnLoad() {
370   // These classes are required for ScopeWithClassLoader. Ensure they are looked
371   // up when loading.
372   JThreadScopeSupport::OnLoad();
373 }
374 
375 /* static */
WithClassLoader(std::function<void ()> && runnable)376 void ThreadScope::WithClassLoader(std::function<void()>&& runnable) {
377   if (cachedOrNull() == nullptr) {
378     ThreadScope ts;
379     JThreadScopeSupport::runStdFunction(std::move(runnable));
380   } else {
381     runnable();
382   }
383 }
384 
385 } // namespace jni
386 } // namespace facebook
387