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