1 /*
2  * Copyright (C) 2018 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 "android-base/logging.h"
18 #include "dex_builder.h"
19 
20 #include <fstream>
21 #include <string>
22 
23 // Adding tests here requires changes in several other places. See README.md in
24 // the view_compiler directory for more information.
25 
26 using namespace startop::dex;
27 using namespace std;
28 
GenerateTrivialDexFile(const string & outdir)29 void GenerateTrivialDexFile(const string& outdir) {
30   DexBuilder dex_file;
31 
32   ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.Trivial")};
33   cbuilder.set_source_file("dex_testcase_generator.cc#GenerateTrivialDexFile");
34 
35   slicer::MemView image{dex_file.CreateImage()};
36   std::ofstream out_file(outdir + "/trivial.dex");
37   out_file.write(image.ptr<const char>(), image.size());
38 }
39 
40 // Generates test cases that test around 1 instruction.
GenerateSimpleTestCases(const string & outdir)41 void GenerateSimpleTestCases(const string& outdir) {
42   DexBuilder dex_file;
43 
44   ClassBuilder cbuilder{dex_file.MakeClass("android.startop.test.testcases.SimpleTests")};
45   cbuilder.set_source_file("dex_testcase_generator.cc#GenerateSimpleTestCases");
46 
47   // int return5() { return 5; }
48   auto return5{cbuilder.CreateMethod("return5", Prototype{TypeDescriptor::Int()})};
49   {
50     LiveRegister r{return5.AllocRegister()};
51     return5.BuildConst4(r, 5);
52     return5.BuildReturn(r);
53   }
54   return5.Encode();
55 
56   // int return5() { return 5; }
57   auto integer_type{TypeDescriptor::FromClassname("java.lang.Integer")};
58   auto returnInteger5{cbuilder.CreateMethod("returnInteger5", Prototype{integer_type})};
59   [&](MethodBuilder& method) {
60     LiveRegister five{method.AllocRegister()};
61     method.BuildConst4(five, 5);
62     LiveRegister object{method.AllocRegister()};
63     method.BuildNew(
64         object, integer_type, Prototype{TypeDescriptor::Void(), TypeDescriptor::Int()}, five);
65     method.BuildReturn(object, /*is_object=*/true);
66   }(returnInteger5);
67   returnInteger5.Encode();
68 
69   // // int returnParam(int x) { return x; }
70   auto returnParam{cbuilder.CreateMethod("returnParam",
71                                          Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
72   returnParam.BuildReturn(Value::Parameter(0));
73   returnParam.Encode();
74 
75   // int returnStringLength(String x) { return x.length(); }
76   auto string_type{TypeDescriptor::FromClassname("java.lang.String")};
77   MethodDeclData string_length{
78       dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()})};
79 
80   auto returnStringLength{
81       cbuilder.CreateMethod("returnStringLength", Prototype{TypeDescriptor::Int(), string_type})};
82   {
83     LiveRegister result = returnStringLength.AllocRegister();
84     returnStringLength.AddInstruction(
85         Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
86     returnStringLength.BuildReturn(result);
87   }
88   returnStringLength.Encode();
89 
90   // int returnIfZero(int x) { if (x == 0) { return 5; } else { return 3; } }
91   MethodBuilder returnIfZero{cbuilder.CreateMethod(
92       "returnIfZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
93   {
94     LiveRegister resultIfZero{returnIfZero.AllocRegister()};
95     Value else_target{returnIfZero.MakeLabel()};
96     returnIfZero.AddInstruction(Instruction::OpWithArgs(
97         Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
98     // else branch
99     returnIfZero.BuildConst4(resultIfZero, 3);
100     returnIfZero.AddInstruction(
101         Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero));
102     // then branch
103     returnIfZero.AddInstruction(
104         Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
105     returnIfZero.BuildConst4(resultIfZero, 5);
106     returnIfZero.AddInstruction(
107         Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfZero));
108   }
109   returnIfZero.Encode();
110 
111   // int returnIfNotZero(int x) { if (x != 0) { return 5; } else { return 3; } }
112   MethodBuilder returnIfNotZero{cbuilder.CreateMethod(
113       "returnIfNotZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
114   {
115     LiveRegister resultIfNotZero{returnIfNotZero.AllocRegister()};
116     Value else_target{returnIfNotZero.MakeLabel()};
117     returnIfNotZero.AddInstruction(Instruction::OpWithArgs(
118         Instruction::Op::kBranchNEqz, /*dest=*/{}, Value::Parameter(0), else_target));
119     // else branch
120     returnIfNotZero.BuildConst4(resultIfNotZero, 3);
121     returnIfNotZero.AddInstruction(
122         Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
123     // then branch
124     returnIfNotZero.AddInstruction(
125         Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
126     returnIfNotZero.BuildConst4(resultIfNotZero, 5);
127     returnIfNotZero.AddInstruction(
128         Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
129   }
130   returnIfNotZero.Encode();
131 
132   // Make sure backwards branches work too.
133   //
134   // Pseudo code for test:
135   // {
136   //   zero = 0;
137   //   result = 1;
138   //   if (zero == 0) goto B;
139   // A:
140   //   return result;
141   // B:
142   //   result = 2;
143   //   if (zero == 0) goto A;
144   //   result = 3;
145   //   return result;
146   // }
147   // If it runs correctly, this test should return 2.
148   MethodBuilder backwardsBranch{
149       cbuilder.CreateMethod("backwardsBranch", Prototype{TypeDescriptor::Int()})};
150   [](MethodBuilder& method) {
151     LiveRegister zero = method.AllocRegister();
152     LiveRegister result = method.AllocRegister();
153     Value labelA = method.MakeLabel();
154     Value labelB = method.MakeLabel();
155     method.BuildConst4(zero, 0);
156     method.BuildConst4(result, 1);
157     method.AddInstruction(
158         Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelB));
159 
160     method.AddInstruction(
161         Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelA));
162     method.BuildReturn(result);
163 
164     method.AddInstruction(
165         Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, labelB));
166     method.BuildConst4(result, 2);
167     method.AddInstruction(
168         Instruction::OpWithArgs(Instruction::Op::kBranchEqz, /*dest=*/{}, zero, labelA));
169 
170     method.BuildConst4(result, 3);
171     method.BuildReturn(result);
172   }(backwardsBranch);
173   backwardsBranch.Encode();
174 
175   // Test that we can make a null value. Basically:
176   //
177   // public static String returnNull() { return null; }
178   MethodBuilder returnNull{cbuilder.CreateMethod("returnNull", Prototype{string_type})};
179   [](MethodBuilder& method) {
180     LiveRegister zero = method.AllocRegister();
181     method.BuildConst4(zero, 0);
182     method.BuildReturn(zero, /*is_object=*/true);
183   }(returnNull);
184   returnNull.Encode();
185 
186   // Test that we can make String literals. Basically:
187   //
188   // public static String makeString() { return "Hello, World!"; }
189   MethodBuilder makeString{cbuilder.CreateMethod("makeString", Prototype{string_type})};
190   [](MethodBuilder& method) {
191     LiveRegister string = method.AllocRegister();
192     method.BuildConstString(string, "Hello, World!");
193     method.BuildReturn(string, /*is_object=*/true);
194   }(makeString);
195   makeString.Encode();
196 
197   // Make sure strings are sorted correctly.
198   //
199   // int returnStringIfZeroAB(int x) { if (x == 0) { return "a"; } else { return "b"; } }
200   MethodBuilder returnStringIfZeroAB{
201       cbuilder.CreateMethod("returnStringIfZeroAB", Prototype{string_type, TypeDescriptor::Int()})};
202   [&](MethodBuilder& method) {
203     LiveRegister resultIfZero{method.AllocRegister()};
204     Value else_target{method.MakeLabel()};
205     method.AddInstruction(Instruction::OpWithArgs(
206         Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
207     // else branch
208     method.BuildConstString(resultIfZero, "b");
209     method.AddInstruction(
210         Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
211     // then branch
212     method.AddInstruction(
213         Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
214     method.BuildConstString(resultIfZero, "a");
215     method.AddInstruction(
216         Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
217     method.Encode();
218   }(returnStringIfZeroAB);
219   // int returnStringIfZeroAB(int x) { if (x == 0) { return "b"; } else { return "a"; } }
220   MethodBuilder returnStringIfZeroBA{
221       cbuilder.CreateMethod("returnStringIfZeroBA", Prototype{string_type, TypeDescriptor::Int()})};
222   [&](MethodBuilder& method) {
223     LiveRegister resultIfZero{method.AllocRegister()};
224     Value else_target{method.MakeLabel()};
225     method.AddInstruction(Instruction::OpWithArgs(
226         Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
227     // else branch
228     method.BuildConstString(resultIfZero, "a");
229     method.AddInstruction(
230         Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
231     // then branch
232     method.AddInstruction(
233         Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
234     method.BuildConstString(resultIfZero, "b");
235     method.AddInstruction(
236         Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
237     method.Encode();
238   }(returnStringIfZeroBA);
239 
240   // Make sure we can invoke static methods that return an object
241   // String invokeStaticReturnObject(int n, int radix) { return java.lang.Integer.toString(n,
242   // radix); }
243   MethodBuilder invokeStaticReturnObject{
244       cbuilder.CreateMethod("invokeStaticReturnObject",
245                             Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
246   [&](MethodBuilder& method) {
247     LiveRegister result{method.AllocRegister()};
248     MethodDeclData to_string{dex_file.GetOrDeclareMethod(
249         TypeDescriptor::FromClassname("java.lang.Integer"),
250         "toString",
251         Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
252     method.AddInstruction(Instruction::InvokeStaticObject(
253         to_string.id, result, Value::Parameter(0), Value::Parameter(1)));
254     method.BuildReturn(result, /*is_object=*/true);
255     method.Encode();
256   }(invokeStaticReturnObject);
257 
258   // Make sure we can invoke virtual methods that return an object
259   // String invokeVirtualReturnObject(String s, int n) { return s.substring(n); }
260   MethodBuilder invokeVirtualReturnObject{cbuilder.CreateMethod(
261       "invokeVirtualReturnObject", Prototype{string_type, string_type, TypeDescriptor::Int()})};
262   [&](MethodBuilder& method) {
263     LiveRegister result{method.AllocRegister()};
264     MethodDeclData substring{dex_file.GetOrDeclareMethod(
265         string_type, "substring", Prototype{string_type, TypeDescriptor::Int()})};
266     method.AddInstruction(Instruction::InvokeVirtualObject(
267         substring.id, result, Value::Parameter(0), Value::Parameter(1)));
268     method.BuildReturn(result, /*is_object=*/true);
269     method.Encode();
270   }(invokeVirtualReturnObject);
271 
272   // Make sure we can cast objects
273   // String castObjectToString(Object o) { return (String)o; }
274   MethodBuilder castObjectToString{cbuilder.CreateMethod(
275       "castObjectToString",
276       Prototype{string_type, TypeDescriptor::FromClassname("java.lang.Object")})};
277   [&](MethodBuilder& method) {
278     const ir::Type* type_def = dex_file.GetOrAddType(string_type.descriptor());
279     method.AddInstruction(
280         Instruction::Cast(Value::Parameter(0), Value::Type(type_def->orig_index)));
281     method.BuildReturn(Value::Parameter(0), /*is_object=*/true);
282     method.Encode();
283   }(castObjectToString);
284 
285   TypeDescriptor test_class = TypeDescriptor::FromClassname("android.startop.test.TestClass");
286 
287   // Read a static field
288   // int readStaticField() { return TestClass.staticInteger; }
289   MethodBuilder readStaticField{
290       cbuilder.CreateMethod("readStaticField", Prototype{TypeDescriptor::Int()})};
291   [&](MethodBuilder& method) {
292     const ir::FieldDecl* field =
293         dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
294     LiveRegister result{method.AllocRegister()};
295     method.AddInstruction(Instruction::GetStaticField(field->orig_index, result));
296     method.BuildReturn(result, /*is_object=*/false);
297     method.Encode();
298   }(readStaticField);
299 
300   // Set a static field
301   // void setStaticField() { TestClass.staticInteger = 7; }
302   MethodBuilder setStaticField{
303       cbuilder.CreateMethod("setStaticField", Prototype{TypeDescriptor::Void()})};
304   [&](MethodBuilder& method) {
305     const ir::FieldDecl* field =
306         dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
307     LiveRegister number{method.AllocRegister()};
308     method.BuildConst4(number, 7);
309     method.AddInstruction(Instruction::SetStaticField(field->orig_index, number));
310     method.BuildReturn();
311     method.Encode();
312   }(setStaticField);
313 
314   // Read an instance field
315   // int readInstanceField(TestClass obj) { return obj.instanceField; }
316   MethodBuilder readInstanceField{
317       cbuilder.CreateMethod("readInstanceField", Prototype{TypeDescriptor::Int(), test_class})};
318   [&](MethodBuilder& method) {
319     const ir::FieldDecl* field =
320         dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
321     LiveRegister result{method.AllocRegister()};
322     method.AddInstruction(Instruction::GetField(field->orig_index, result, Value::Parameter(0)));
323     method.BuildReturn(result, /*is_object=*/false);
324     method.Encode();
325   }(readInstanceField);
326 
327   // Set an instance field
328   // void setInstanceField(TestClass obj) { obj.instanceField = 7; }
329   MethodBuilder setInstanceField{
330       cbuilder.CreateMethod("setInstanceField", Prototype{TypeDescriptor::Void(), test_class})};
331   [&](MethodBuilder& method) {
332     const ir::FieldDecl* field =
333         dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
334     LiveRegister number{method.AllocRegister()};
335     method.BuildConst4(number, 7);
336     method.AddInstruction(Instruction::SetField(field->orig_index, Value::Parameter(0), number));
337     method.BuildReturn();
338     method.Encode();
339   }(setInstanceField);
340 
341   slicer::MemView image{dex_file.CreateImage()};
342   std::ofstream out_file(outdir + "/simple.dex");
343   out_file.write(image.ptr<const char>(), image.size());
344 }
345 
main(int argc,char ** argv)346 int main(int argc, char** argv) {
347   CHECK_EQ(argc, 2);
348 
349   string outdir = argv[1];
350 
351   GenerateTrivialDexFile(outdir);
352   GenerateSimpleTestCases(outdir);
353 }
354