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 #include <mutex>
20 #include <vector>
21
22 #include <fbjni/detail/utf8.h>
23
24 namespace facebook {
25 namespace jni {
26
initialize(JavaVM * vm,std::function<void ()> && init_fn)27 jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept {
28 // TODO (t7832883): DTRT when we have exception pointers
29 static auto error_msg = std::string{"Failed to initialize fbjni"};
30 static bool error_occured = [vm] {
31 bool retVal = false;
32 try {
33 Environment::initialize(vm);
34 } catch (std::exception& ex) {
35 retVal = true;
36 try {
37 error_msg = std::string{"Failed to initialize fbjni: "} + ex.what();
38 } catch (...) {
39 // Ignore, we already have a fall back message
40 }
41 } catch (...) {
42 retVal = true;
43 }
44 return retVal;
45 }();
46
47 try {
48 if (error_occured) {
49 throw std::runtime_error(error_msg);
50 }
51
52 init_fn();
53 } catch (const std::exception& e) {
54 FBJNI_LOGE("error %s", e.what());
55 translatePendingCppExceptionToJavaException();
56 } catch (...) {
57 translatePendingCppExceptionToJavaException();
58 // So Java will handle the translated exception, fall through and
59 // return a good version number.
60 }
61 return JNI_VERSION_1_6;
62 }
63
64 namespace detail {
65
findClass(JNIEnv * env,const char * name)66 jclass findClass(JNIEnv* env, const char* name) {
67 if (!env) {
68 throw std::runtime_error("Unable to retrieve JNIEnv*.");
69 }
70 jclass cls = env->FindClass(name);
71 FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
72 return cls;
73 }
74
75 } // namespace detail
76
findClassLocal(const char * name)77 local_ref<JClass> findClassLocal(const char* name) {
78 return adopt_local(detail::findClass(detail::currentOrNull(), name));
79 }
80
findClassStatic(const char * name)81 alias_ref<JClass> findClassStatic(const char* name) {
82 JNIEnv* env = detail::currentOrNull();
83 auto cls = adopt_local(detail::findClass(env, name));
84 auto leaking_ref = (jclass)env->NewGlobalRef(cls.get());
85 FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref);
86 return wrap_alias(leaking_ref);
87 }
88
89 // jstring
90 // /////////////////////////////////////////////////////////////////////////////////////////
91
toStdString() const92 std::string JString::toStdString() const {
93 const auto env = Environment::current();
94 auto utf16String = JStringUtf16Extractor(env, self());
95 return detail::utf16toUTF8(utf16String.chars(), utf16String.length());
96 }
97
toU16String() const98 std::u16string JString::toU16String() const {
99 const auto env = Environment::current();
100 auto utf16String = JStringUtf16Extractor(env, self());
101 if (!utf16String.chars() || utf16String.length() == 0) {
102 return {};
103 }
104 return std::u16string(
105 reinterpret_cast<const char16_t*>(utf16String.chars()),
106 utf16String.length());
107 }
108
make_jstring(const char * utf8)109 local_ref<JString> make_jstring(const char* utf8) {
110 if (!utf8) {
111 return {};
112 }
113 const auto env = Environment::current();
114 size_t len;
115 size_t modlen =
116 detail::modifiedLength(reinterpret_cast<const uint8_t*>(utf8), &len);
117 jstring result;
118 if (modlen == len) {
119 // The only difference between utf8 and modifiedUTF8 is in encoding 4-byte
120 // UTF8 chars and '\0' that is encoded on 2 bytes.
121 //
122 // Since modifiedUTF8-encoded string can be no shorter than it's UTF8
123 // conterpart we know that if those two strings are of the same length we
124 // don't need to do any conversion -> no 4-byte chars nor '\0'.
125 result = env->NewStringUTF(utf8);
126 } else {
127 auto modified = std::vector<char>(modlen + 1); // allocate extra byte for \0
128 detail::utf8ToModifiedUTF8(
129 reinterpret_cast<const uint8_t*>(utf8),
130 len,
131 reinterpret_cast<uint8_t*>(modified.data()),
132 modified.size());
133 result = env->NewStringUTF(modified.data());
134 }
135 FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
136 return adopt_local(result);
137 }
138
make_jstring(const std::u16string & utf16)139 local_ref<JString> make_jstring(const std::u16string& utf16) {
140 if (utf16.empty()) {
141 return {};
142 }
143 const auto env = Environment::current();
144 static_assert(
145 sizeof(jchar) == sizeof(std::u16string::value_type),
146 "Expecting jchar to be the same size as std::u16string::CharT");
147 jstring result = env->NewString(
148 reinterpret_cast<const jchar*>(utf16.c_str()), utf16.size());
149 FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
150 return adopt_local(result);
151 }
152
153 // JniPrimitiveArrayFunctions
154 // //////////////////////////////////////////////////////////////////////
155
156 #pragma push_macro("DEFINE_PRIMITIVE_METHODS")
157 #undef DEFINE_PRIMITIVE_METHODS
158 #define DEFINE_PRIMITIVE_METHODS(TYPE, NAME, SMALLNAME) \
159 \
160 template <> \
161 TYPE* JPrimitiveArray<TYPE##Array>::getElements(jboolean* isCopy) { \
162 auto env = Environment::current(); \
163 TYPE* res = env->Get##NAME##ArrayElements(self(), isCopy); \
164 FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
165 return res; \
166 } \
167 \
168 template <> \
169 void JPrimitiveArray<TYPE##Array>::releaseElements( \
170 TYPE* elements, jint mode) { \
171 auto env = Environment::current(); \
172 env->Release##NAME##ArrayElements(self(), elements, mode); \
173 FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
174 } \
175 \
176 template <> \
177 void JPrimitiveArray<TYPE##Array>::getRegion( \
178 jsize start, jsize length, TYPE* buf) { \
179 auto env = Environment::current(); \
180 env->Get##NAME##ArrayRegion(self(), start, length, buf); \
181 FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
182 } \
183 \
184 template <> \
185 void JPrimitiveArray<TYPE##Array>::setRegion( \
186 jsize start, jsize length, const TYPE* elements) { \
187 auto env = Environment::current(); \
188 env->Set##NAME##ArrayRegion(self(), start, length, elements); \
189 FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \
190 } \
191 \
192 local_ref<TYPE##Array> make_##SMALLNAME##_array(jsize size) { \
193 auto array = Environment::current()->New##NAME##Array(size); \
194 FACEBOOK_JNI_THROW_EXCEPTION_IF(!array); \
195 return adopt_local(array); \
196 } \
197 \
198 template <> \
199 local_ref<TYPE##Array> JArray##NAME::newArray(size_t count) { \
200 return make_##SMALLNAME##_array(count); \
201 }
202
203 DEFINE_PRIMITIVE_METHODS(jboolean, Boolean, boolean)
204 DEFINE_PRIMITIVE_METHODS(jbyte, Byte, byte)
205 DEFINE_PRIMITIVE_METHODS(jchar, Char, char)
206 DEFINE_PRIMITIVE_METHODS(jshort, Short, short)
207 DEFINE_PRIMITIVE_METHODS(jint, Int, int)
208 DEFINE_PRIMITIVE_METHODS(jlong, Long, long)
209 DEFINE_PRIMITIVE_METHODS(jfloat, Float, float)
210 DEFINE_PRIMITIVE_METHODS(jdouble, Double, double)
211 #pragma pop_macro("DEFINE_PRIMITIVE_METHODS")
212
213 namespace detail {
214
getNativePointer() const215 detail::BaseHybridClass* HybridDestructor::getNativePointer() const {
216 static auto pointerField =
217 javaClassStatic()->getField<jlong>("mNativePointer");
218 auto* value =
219 reinterpret_cast<detail::BaseHybridClass*>(getFieldValue(pointerField));
220 if (!value) {
221 throwNewJavaException(
222 "java/lang/NullPointerException", "java.lang.NullPointerException");
223 }
224 return value;
225 }
226
setNativePointer(std::unique_ptr<detail::BaseHybridClass> new_value)227 void HybridDestructor::setNativePointer(
228 std::unique_ptr<detail::BaseHybridClass> new_value) {
229 static auto pointerField =
230 javaClassStatic()->getField<jlong>("mNativePointer");
231 auto old_value = std::unique_ptr<detail::BaseHybridClass>(
232 reinterpret_cast<detail::BaseHybridClass*>(getFieldValue(pointerField)));
233 if (new_value && old_value) {
234 FBJNI_LOGF("Attempt to set C++ native pointer twice");
235 }
236 setFieldValue(pointerField, reinterpret_cast<jlong>(new_value.release()));
237 }
238
239 } // namespace detail
240
241 // Internal debug
242 // /////////////////////////////////////////////////////////////////////////////////
243
244 namespace internal {
245
246 ReferenceStats g_reference_stats;
247
reset()248 void facebook::jni::internal::ReferenceStats::reset() noexcept {
249 locals_deleted = globals_deleted = weaks_deleted = 0;
250 }
251
252 } // namespace internal
253
254 } // namespace jni
255 } // namespace facebook
256