1 /*
2  * Copyright (C) 2024 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 "jni_stub_hash_map.h"
18 
19 #include <gtest/gtest.h>
20 
21 #include <memory>
22 #include <ostream>
23 #include <string>
24 #include <string_view>
25 #include <utility>
26 #include <vector>
27 
28 #include "android-base/logging.h"
29 #include "android-base/stringprintf.h"
30 #include "arch/instruction_set.h"
31 #include "art_method.h"
32 #include "base/array_ref.h"
33 #include "base/locks.h"
34 #include "base/utils.h"
35 #include "class_linker.h"
36 #include "common_compiler_test.h"
37 #include "common_compiler_test.cc"
38 #include "compiler.h"
39 #include "gc/heap.h"
40 #include "gc/space/image_space.h"
41 #include "handle.h"
42 #include "handle_scope.h"
43 #include "handle_scope-inl.h"
44 #include "image.h"
45 #include "image-inl.h"
46 #include "jni.h"
47 #include "mirror/class.h"
48 #include "mirror/class_loader.h"
49 #include "mirror/dex_cache.h"
50 #include "obj_ptr.h"
51 #include "runtime.h"
52 #include "scoped_thread_state_change.h"
53 #include "strstream"
54 
55 namespace art HIDDEN {
56 
57 // Teach gtest how to print the ArrayRef<const uint8_t>. The customized output is easier used
58 // for converting to assembly instructions.
PrintTo(const ArrayRef<const uint8_t> & array,std::ostream * os)59 static void PrintTo(const ArrayRef<const uint8_t>& array, std::ostream* os) {
60   *os << "[[[";
61   for (const uint8_t& element : array) {
62     *os << " ";
63     *os << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(element);
64   }
65   *os << " ]]]";
66 }
67 
68 class JniStubHashMapTest : public CommonCompilerTest {
69  protected:
JniStubHashMapTest()70   JniStubHashMapTest()
71       : jni_stub_hash_map_(JniStubKeyHash(kRuntimeISA), JniStubKeyEquals(kRuntimeISA)) {
72     if (kRuntimeISA == InstructionSet::kArm64 || kRuntimeISA == InstructionSet::kX86_64) {
73       // Only arm64 and x86_64 use strict check.
74       strict_check_ = true;
75     } else {
76       // Other archs use loose check.
77       strict_check_ = false;
78     }
79   }
80 
SetStrictCheck(bool value)81   void SetStrictCheck(bool value) {
82     strict_check_ = value;
83   }
84 
SetUpForTest()85   void SetUpForTest() {
86     ScopedObjectAccess soa(Thread::Current());
87     jobject jclass_loader = LoadDex("MyClassNatives");
88     StackHandleScope<1> hs(soa.Self());
89     Handle<mirror::ClassLoader> class_loader(
90         hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader)));
91     pointer_size_ = class_linker_->GetImagePointerSize();
92     ObjPtr<mirror::Class> klass =
93         class_linker_->FindClass(soa.Self(), "LMyClassNatives;", class_loader);
94     ASSERT_TRUE(klass != nullptr);
95     jklass_ = soa.AddLocalReference<jclass>(klass);
96   }
97 
SetBaseMethod(std::string_view base_method_name,std::string_view base_method_sig)98   void SetBaseMethod(std::string_view base_method_name, std::string_view base_method_sig) {
99     jni_stub_hash_map_.clear();
100     Thread* self = Thread::Current();
101     ScopedObjectAccess soa(self);
102     ObjPtr<mirror::Class> klass = soa.Decode<mirror::Class>(jklass_);
103     base_method_ = klass->FindClassMethod(base_method_name, base_method_sig, pointer_size_);
104     ASSERT_TRUE(base_method_ != nullptr);
105     ASSERT_TRUE(base_method_->IsNative());
106 
107     OneCompiledMethodStorage base_method_storage;
108     StackHandleScope<1> hs(self);
109     std::unique_ptr<Compiler> compiler(
110         Compiler::Create(*compiler_options_, &base_method_storage));
111     const DexFile& dex_file = *base_method_->GetDexFile();
112     Handle<mirror::DexCache> dex_cache =
113         hs.NewHandle(GetClassLinker()->FindDexCache(self, dex_file));
114     compiler->JniCompile(base_method_->GetAccessFlags(),
115                          base_method_->GetDexMethodIndex(),
116                          dex_file,
117                          dex_cache);
118     ArrayRef<const uint8_t> code = base_method_storage.GetCode();
119     base_method_code_.assign(code.begin(), code.end());
120 
121     jni_stub_hash_map_.insert(std::make_pair(JniStubKey(base_method_), base_method_));
122   }
123 
CompareMethod(std::string_view cmp_method_name,std::string_view cmp_method_sig)124   void CompareMethod(std::string_view cmp_method_name, std::string_view cmp_method_sig) {
125     Thread* self = Thread::Current();
126     ScopedObjectAccess soa(self);
127     ObjPtr<mirror::Class> klass = soa.Decode<mirror::Class>(jklass_);
128     ArtMethod* cmp_method = klass->FindClassMethod(cmp_method_name, cmp_method_sig, pointer_size_);
129     ASSERT_TRUE(cmp_method != nullptr);
130     ASSERT_TRUE(cmp_method->IsNative());
131 
132     OneCompiledMethodStorage cmp_method_storage;
133     StackHandleScope<1> hs(self);
134     std::unique_ptr<Compiler> compiler(
135         Compiler::Create(*compiler_options_, &cmp_method_storage));
136     const DexFile& dex_file = *cmp_method->GetDexFile();
137     Handle<mirror::DexCache> dex_cache =
138         hs.NewHandle(GetClassLinker()->FindDexCache(self, dex_file));
139     compiler->JniCompile(cmp_method->GetAccessFlags(),
140                          cmp_method->GetDexMethodIndex(),
141                          dex_file,
142                          dex_cache);
143 
144     ArrayRef<const uint8_t> method_code = ArrayRef<const uint8_t>(base_method_code_);
145     ArrayRef<const uint8_t> cmp_method_code = cmp_method_storage.GetCode();
146     auto it = jni_stub_hash_map_.find(JniStubKey(cmp_method));
147     if (it != jni_stub_hash_map_.end()) {
148       ASSERT_EQ(method_code, cmp_method_code)
149           << "base method: " << base_method_->PrettyMethod() << ", compared method: "
150           << cmp_method->PrettyMethod();
151     } else if (strict_check_){
152       // If the compared method maps to a different entry, then its compiled JNI stub should be
153       // also different from the base one.
154       ASSERT_NE(method_code, cmp_method_code)
155           << "base method: " << base_method_->PrettyMethod() << ", compared method: "
156           << cmp_method->PrettyMethod();
157     }
158   }
159 
160   bool strict_check_;
161   JniStubHashMap<ArtMethod*> jni_stub_hash_map_;
162   PointerSize pointer_size_;
163   jclass jklass_;
164   ArtMethod* base_method_;
165   std::vector<uint8_t> base_method_code_;
166 };
167 
168 class JniStubHashMapBootImageTest : public CommonRuntimeTest {
169  protected:
SetUpRuntimeOptions(RuntimeOptions * options)170   void SetUpRuntimeOptions(RuntimeOptions* options) override {
171     std::string runtime_args_image;
172     runtime_args_image = android::base::StringPrintf("-Ximage:%s", GetCoreArtLocation().c_str());
173     options->push_back(std::make_pair(runtime_args_image, nullptr));
174   }
175 };
176 
TEST_F(JniStubHashMapTest,ReturnType)177 TEST_F(JniStubHashMapTest, ReturnType) {
178   SetUpForTest();
179   SetBaseMethod("fooI", "(I)I");
180   CompareMethod("fooI_V", "(I)V");
181   CompareMethod("fooI_B", "(I)B");
182   CompareMethod("fooI_C", "(I)C");
183   CompareMethod("fooI_S", "(I)S");
184   CompareMethod("fooI_Z", "(I)Z");
185   CompareMethod("fooI_J", "(I)J");
186   CompareMethod("fooI_F", "(I)F");
187   CompareMethod("fooI_D", "(I)D");
188   CompareMethod("fooI_L", "(I)Ljava/lang/Object;");
189 }
190 
TEST_F(JniStubHashMapTest,ArgType)191 TEST_F(JniStubHashMapTest, ArgType) {
192   SetUpForTest();
193   SetBaseMethod("sfooI", "(I)I");
194   CompareMethod("sfooB", "(B)I");
195   CompareMethod("sfooC", "(C)I");
196   CompareMethod("sfooS", "(S)I");
197   CompareMethod("sfooZ", "(Z)I");
198   CompareMethod("sfooL", "(Ljava/lang/Object;)I");
199 }
200 
TEST_F(JniStubHashMapTest,FloatingPointArg)201 TEST_F(JniStubHashMapTest, FloatingPointArg) {
202   SetUpForTest();
203   SetBaseMethod("sfooI", "(I)I");
204   CompareMethod("sfoo7FI", "(FFFFFFFI)I");
205   CompareMethod("sfoo3F5DI", "(FFFDDDDDI)I");
206   CompareMethod("sfoo3F6DI", "(FFFDDDDDDI)I");
207 }
208 
TEST_F(JniStubHashMapTest,IntegralArg)209 TEST_F(JniStubHashMapTest, IntegralArg) {
210   SetUpForTest();
211   SetBaseMethod("fooL", "(Ljava/lang/Object;)I");
212   CompareMethod("fooL4I", "(Ljava/lang/Object;IIII)I");
213   CompareMethod("fooL5I", "(Ljava/lang/Object;IIIII)I");
214   CompareMethod("fooL3IJC", "(Ljava/lang/Object;IIIJC)I");
215   CompareMethod("fooL3IJCS", "(Ljava/lang/Object;IIIJCS)I");
216 }
217 
TEST_F(JniStubHashMapTest,StackOffsetMatters)218 TEST_F(JniStubHashMapTest, StackOffsetMatters) {
219   SetUpForTest();
220   SetBaseMethod("foo7FDF", "(FFFFFFFDF)I");
221   CompareMethod("foo9F", "(FFFFFFFFF)I");
222   CompareMethod("foo7FIFF", "(FFFFFFFIFF)I");
223   SetBaseMethod("foo5IJI", "(IIIIIJI)I");
224   CompareMethod("foo7I", "(IIIIIII)I");
225   CompareMethod("foo5IFII", "(IIIIIFII)I");
226   SetBaseMethod("fooFDL", "(FDLjava/lang/Object;)I");
227   CompareMethod("foo2FL", "(FFLjava/lang/Object;)I");
228   CompareMethod("foo3FL", "(FFFLjava/lang/Object;)I");
229   CompareMethod("foo2FIL", "(FFILjava/lang/Object;)I");
230 }
231 
TEST_F(JniStubHashMapTest,IntLikeRegsMatters)232 TEST_F(JniStubHashMapTest, IntLikeRegsMatters) {
233   SetUpForTest();
234   SetBaseMethod("fooICFL", "(ICFLjava/lang/Object;)I");
235   CompareMethod("foo2IFL", "(IIFLjava/lang/Object;)I");
236   CompareMethod("fooICIL", "(ICILjava/lang/Object;)I");
237 }
238 
TEST_F(JniStubHashMapTest,FastNative)239 TEST_F(JniStubHashMapTest, FastNative) {
240   SetUpForTest();
241   SetBaseMethod("fooI_Fast", "(I)I");
242   CompareMethod("fooI_Z_Fast", "(I)Z");
243   CompareMethod("fooI_J_Fast", "(I)J");
244   SetBaseMethod("fooICFL_Fast", "(ICFLjava/lang/Object;)I");
245   CompareMethod("foo2IFL_Fast", "(IIFLjava/lang/Object;)I");
246   CompareMethod("fooICIL_Fast", "(ICILjava/lang/Object;)I");
247   SetBaseMethod("fooFDL_Fast", "(FDLjava/lang/Object;)I");
248   CompareMethod("foo2FL_Fast", "(FFLjava/lang/Object;)I");
249   CompareMethod("foo3FL_Fast", "(FFFLjava/lang/Object;)I");
250   CompareMethod("foo2FIL_Fast", "(FFILjava/lang/Object;)I");
251   SetBaseMethod("foo7F_Fast", "(FFFFFFF)I");
252   CompareMethod("foo3F5D_Fast", "(FFFDDDDD)I");
253   CompareMethod("foo3F6D_Fast", "(FFFDDDDDD)I");
254   SetBaseMethod("fooL5I_Fast", "(Ljava/lang/Object;IIIII)I");
255   CompareMethod("fooL3IJC_Fast", "(Ljava/lang/Object;IIIJC)I");
256   CompareMethod("fooL3IJCS_Fast", "(Ljava/lang/Object;IIIJCS)I");
257 }
258 
TEST_F(JniStubHashMapTest,CriticalNative)259 TEST_F(JniStubHashMapTest, CriticalNative) {
260   SetUpForTest();
261   if (kRuntimeISA == InstructionSet::kX86_64) {
262     // In x86_64, the return type seems be ignored in critical function.
263     SetStrictCheck(false);
264   }
265   SetBaseMethod("returnInt_Critical", "()I");
266   CompareMethod("returnDouble_Critical", "()D");
267   CompareMethod("returnLong_Critical", "()J");
268   SetBaseMethod("foo7F_Critical", "(FFFFFFF)I");
269   CompareMethod("foo3F5D_Critical", "(FFFDDDDD)I");
270   CompareMethod("foo3F6D_Critical", "(FFFDDDDDD)I");
271 }
272 
TEST_F(JniStubHashMapBootImageTest,BootImageSelfCheck)273 TEST_F(JniStubHashMapBootImageTest, BootImageSelfCheck) {
274   std::vector<gc::space::ImageSpace*> image_spaces =
275       Runtime::Current()->GetHeap()->GetBootImageSpaces();
276   ASSERT_TRUE(!image_spaces.empty());
277   for (gc::space::ImageSpace* space : image_spaces) {
278     const ImageHeader& header = space->GetImageHeader();
279     PointerSize ptr_size = class_linker_->GetImagePointerSize();
280     auto visitor = [&](ArtMethod& method) REQUIRES_SHARED(Locks::mutator_lock_) {
281       if (method.IsNative() && !method.IsIntrinsic()) {
282         const void* boot_jni_stub = class_linker_->FindBootJniStub(JniStubKey(&method));
283         if (boot_jni_stub != nullptr) {
284           const void* cmp_jni_stub = method.GetOatMethodQuickCode(ptr_size);
285           size_t boot_jni_stub_size =
286               OatQuickMethodHeader::FromEntryPoint(boot_jni_stub)->GetCodeSize();
287           size_t cmp_jni_stub_size =
288               OatQuickMethodHeader::FromEntryPoint(cmp_jni_stub)->GetCodeSize();
289           ArrayRef<const uint8_t> boot_jni_stub_array = ArrayRef(
290               reinterpret_cast<const uint8_t*>(EntryPointToCodePointer(boot_jni_stub)),
291               boot_jni_stub_size);
292           ArrayRef<const uint8_t> cmp_jni_stub_array = ArrayRef(
293               reinterpret_cast<const uint8_t*>(EntryPointToCodePointer(cmp_jni_stub)),
294               cmp_jni_stub_size);
295           ASSERT_EQ(boot_jni_stub_array, cmp_jni_stub_array)
296               << "method: " << method.PrettyMethod() << ", size = " << cmp_jni_stub_size;
297         }
298       }
299     };
300     header.VisitPackedArtMethods(visitor, space->Begin(), ptr_size);
301   }
302 }
303 
304 }  // namespace art
305