1 /*
2 * Copyright (C) 2023 The Android Open Source Project
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 "guest_jni_trampolines.h"
18
19 #include <dlfcn.h>
20 #include <jni.h>
21 #include <stdint.h>
22 #include <cstddef>
23
24 #include "berberis/base/bit_util.h"
25 #include "berberis/base/logging.h"
26 #include "berberis/base/stringprintf.h"
27 #include "berberis/guest_abi/guest_call.h"
28 #include "berberis/guest_loader/guest_loader.h"
29 #include "berberis/guest_state/guest_addr.h"
30
31 namespace berberis {
32
33 namespace {
34
DlOpenLibicuNoLoad(const char * libname,GuestLoader * loader)35 void* DlOpenLibicuNoLoad(const char* libname, GuestLoader* loader) {
36 android_dlextinfo extinfo;
37 extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
38 extinfo.library_namespace = loader->GetExportedNamespace("com_android_i18n");
39 return loader->DlOpenExt(libname, RTLD_NOLOAD, &extinfo);
40 }
41
42 template <bool kIsStatic = false>
GetLocaleMethodId(JNIEnv * env,const char * name,const char * signature)43 jmethodID GetLocaleMethodId(JNIEnv* env, const char* name, const char* signature) {
44 auto jdeleter = [env](jobject obj) { env->DeleteLocalRef(obj); };
45 std::unique_ptr<_jclass, decltype(jdeleter)> locale_class(env->FindClass("java/util/Locale"),
46 jdeleter);
47 if (kIsStatic) {
48 return env->GetStaticMethodID(locale_class.get(), name, signature);
49 } else {
50 return env->GetMethodID(locale_class.get(), name, signature);
51 }
52 }
53
GetLocaleStaticMethodId(JNIEnv * env,const char * name,const char * signature)54 jmethodID GetLocaleStaticMethodId(JNIEnv* env, const char* name, const char* signature) {
55 return GetLocaleMethodId<true>(env, name, signature);
56 }
57
GuestCall_uloc_setDefault(GuestAddr addr,const char * tag)58 void GuestCall_uloc_setDefault(GuestAddr addr, const char* tag) {
59 CHECK_NE(addr, berberis::kNullGuestAddr);
60 berberis::GuestCall call;
61 int err = 0;
62 #if defined(BERBERIS_GUEST_ILP32)
63 call.AddArgInt32(bit_cast<uint32_t>(tag));
64 call.AddArgInt32(bit_cast<uint32_t>(&err));
65 #elif defined(BERBERIS_GUEST_LP64)
66 call.AddArgInt64(bit_cast<uint64_t>(tag));
67 call.AddArgInt64(bit_cast<uint64_t>(&err));
68 #else
69 #error "Unsupported guest arch"
70 #endif
71 call.RunVoid(addr);
72 // If error, we just skip guest setDefault.
73 }
74
75 // from external/icu/icu4c/source/common/unicode/uversion.h
76 using UVersionInfo = uint8_t[4];
77
GuestCall_u_getVersion(GuestAddr addr,UVersionInfo version_info)78 void GuestCall_u_getVersion(GuestAddr addr, UVersionInfo version_info) {
79 CHECK_NE(addr, berberis::kNullGuestAddr);
80 berberis::GuestCall call;
81 #if defined(BERBERIS_GUEST_ILP32)
82 call.AddArgInt32(bit_cast<uint32_t>(version_info));
83 #elif defined(BERBERIS_GUEST_LP64)
84 call.AddArgInt64(bit_cast<uint64_t>(version_info));
85 #else
86 #error "Unsupported guest arch"
87 #endif
88 call.RunVoid(addr);
89 }
90
GuestCall_uloc_canonicalize(GuestAddr addr,const char * tag,char * canonical_tag,size_t size)91 bool GuestCall_uloc_canonicalize(GuestAddr addr,
92 const char* tag,
93 char* canonical_tag,
94 size_t size) {
95 CHECK_NE(addr, berberis::kNullGuestAddr);
96 berberis::GuestCall call;
97 int err = 0;
98 #if defined(BERBERIS_GUEST_ILP32)
99 call.AddArgInt32(bit_cast<uint32_t>(tag));
100 call.AddArgInt32(bit_cast<uint32_t>(canonical_tag));
101 call.AddArgInt32(size);
102 call.AddArgInt32(bit_cast<uint32_t>(&err));
103 #elif defined(BERBERIS_GUEST_LP64)
104 call.AddArgInt64(bit_cast<uint64_t>(tag));
105 call.AddArgInt64(bit_cast<uint64_t>(canonical_tag));
106 call.AddArgInt64(size);
107 call.AddArgInt64(bit_cast<uint64_t>(&err));
108 #else
109 #error "Unsupported guest arch"
110 #endif
111 call.RunResInt32(addr);
112 return err > 0;
113 }
114
InitLocaleCanonicalTag(JNIEnv * env,GuestAddr uloc_canonicalize_addr,jobject locale,char * canonical_tag,size_t size)115 bool InitLocaleCanonicalTag(JNIEnv* env,
116 GuestAddr uloc_canonicalize_addr,
117 jobject locale,
118 char* canonical_tag,
119 size_t size) {
120 static auto Locale_toLanguageTag_method_id =
121 GetLocaleMethodId(env, "toLanguageTag", "()Ljava/lang/String;");
122 jstring java_tag =
123 static_cast<jstring>(env->CallObjectMethod(locale, Locale_toLanguageTag_method_id));
124 jboolean is_copy;
125 const char* tag = env->GetStringUTFChars(java_tag, &is_copy);
126 // It'd be sufficient to call native uloc_canonicalize here, but we don't want
127 // to add libicu dependency here just for this purpose.
128 bool is_error = GuestCall_uloc_canonicalize(uloc_canonicalize_addr, tag, canonical_tag, size);
129 if (is_error) {
130 return true;
131 }
132 env->ReleaseStringUTFChars(java_tag, tag);
133 return false;
134 }
135
136 } // namespace
137
JNIEnv_CallStaticVoidMethodV_ForGuest(JNIEnv * env,jobject,jmethodID method_id,jvalue * args)138 void JNIEnv_CallStaticVoidMethodV_ForGuest(JNIEnv* env,
139 jobject /* obj */,
140 jmethodID method_id,
141 jvalue* args) {
142 // If we are calling Locale_uloc_setDefault, call it for guest as well. We are using the original
143 // libicuuc which holds its own copy of the default state. Note that this won't help if java calls
144 // setDefault directly. See b/202779669.
145 static auto Locale_setDefault_method_id =
146 GetLocaleStaticMethodId(env, "setDefault", "(Ljava/util/Locale;)V");
147 if (method_id != Locale_setDefault_method_id) {
148 return;
149 }
150 // setDefault has single arg - locale.
151 auto locale = args->l;
152
153 auto* loader = GuestLoader::GetInstance();
154 void* libicu = DlOpenLibicuNoLoad("libicu.so", loader);
155 if (libicu == nullptr) {
156 // Skip guest setDefault if the library hasn't been loaded.
157 return;
158 }
159
160 // Initialize canonical_tag argument for setDefault.
161
162 // from external/icu/libicu/ndk_headers/unicode/uloc.h
163 size_t ULOC_FULLNAME_CAPACITY = 157;
164 char canonical_tag[ULOC_FULLNAME_CAPACITY];
165 bool is_err = InitLocaleCanonicalTag(env,
166 loader->DlSym(libicu, "uloc_canonicalize"),
167 locale,
168 canonical_tag,
169 sizeof(canonical_tag));
170 if (is_err) {
171 // Skip guest setDefault if tag cannot be canonicalized.
172 return;
173 }
174
175 // Stable libicu.so doesn't export uloc_setDefault since it requires the default to be set from
176 // java to keep native and java in sync. So we'll call it from versioned libicuuc.so, but first
177 // get the version from libicu.so. Note that since ICU is an apex and potentially can be updated
178 // dynamically it's disallowed to read its version info from headers during the build time.
179
180 UVersionInfo version_info;
181 GuestCall_u_getVersion(loader->DlSym(libicu, "u_getVersion"), version_info);
182
183 void* libicuuc = DlOpenLibicuNoLoad("libicuuc.so", loader);
184 if (libicuuc == nullptr) {
185 // Skip guest setDefault if the library hasn't been loaded.
186 return;
187 }
188
189 auto uloc_setDefault_versioned_name = StringPrintf("uloc_setDefault_%u", version_info[0]);
190 GuestCall_uloc_setDefault(loader->DlSym(libicuuc, uloc_setDefault_versioned_name.c_str()),
191 canonical_tag);
192 }
193
194 } // namespace berberis
195