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