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