blob: 921a096dd32f78e8f49d1812e650bd158b175554 [file] [log] [blame]
Alex Lighteb7c1442015-08-31 13:17:42 -07001#!/usr/bin/python3
2#
3# Copyright (C) 2015 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Generate Smali test files for test 961.
19"""
20
21import os
22import sys
23from pathlib import Path
24
25BUILD_TOP = os.getenv("ANDROID_BUILD_TOP")
26if BUILD_TOP is None:
27 print("ANDROID_BUILD_TOP not set. Please run build/envsetup.sh", file=sys.stderr)
28 sys.exit(1)
29
30# Allow us to import utils and mixins.
31sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python"))
32
33from testgen.utils import get_copyright, subtree_sizes, gensym, filter_blanks
34import testgen.mixins as mixins
35
36from functools import total_ordering
37import itertools
38import string
39
40# The max depth the type tree can have. Includes the class object in the tree.
41# Increasing this increases the number of generated files significantly. This
42# value was chosen as it is fairly quick to run and very comprehensive, checking
43# every possible interface tree up to 5 layers deep.
44MAX_IFACE_DEPTH = 5
45
46class MainClass(mixins.DumpMixin, mixins.Named, mixins.SmaliFileMixin):
47 """
48 A Main.smali file containing the Main class and the main function. It will run
49 all the test functions we have.
50 """
51
52 MAIN_CLASS_TEMPLATE = """{copyright}
53
54.class public LMain;
55.super Ljava/lang/Object;
56
57# class Main {{
58
59.method public constructor <init>()V
60 .registers 1
61 invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V
62 return-void
63.end method
64
65{test_groups}
66
67{main_func}
68
69# }}
70"""
71
72 MAIN_FUNCTION_TEMPLATE = """
73# public static void main(String[] args) {{
74.method public static main([Ljava/lang/String;)V
75 .locals 2
76 sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
77
78 {test_group_invoke}
79
80 return-void
81.end method
82# }}
83"""
84
85 TEST_GROUP_INVOKE_TEMPLATE = """
86# {test_name}();
87 invoke-static {{}}, {test_name}()V
88"""
89
90 def __init__(self):
91 """
92 Initialize this MainClass. We start out with no tests.
93 """
94 self.tests = set()
95
96 def get_expected(self):
97 """
98 Get the expected output of this test.
99 """
100 all_tests = sorted(self.tests)
101 return filter_blanks("\n".join(a.get_expected() for a in all_tests))
102
103 def add_test(self, ty):
104 """
105 Add a test for the concrete type 'ty'
106 """
107 self.tests.add(Func(ty))
108
109 def get_name(self):
110 """
111 Get the name of this class
112 """
113 return "Main"
114
115 def __str__(self):
116 """
117 Print the MainClass smali code.
118 """
119 all_tests = sorted(self.tests)
120 test_invoke = ""
121 test_groups = ""
122 for t in all_tests:
123 test_groups += str(t)
124 for t in all_tests:
125 test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name())
126 main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke)
127
128 return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright("smali"),
129 test_groups = test_groups,
130 main_func = main_func)
131
132class Func(mixins.Named, mixins.NameComparableMixin):
133 """
134 A function that tests the functionality of a concrete type. Should only be
135 constructed by MainClass.add_test.
136 """
137
138 TEST_FUNCTION_TEMPLATE = """
139# public static void {fname}() {{
140# try {{
141# {farg} v = new {farg}();
142# System.out.printf("%s calls default method on %s\\n",
143# v.CalledClassName(),
144# v.CalledInterfaceName());
145# return;
146# }} catch (Error e) {{
147# e.printStackTrace(System.out);
148# return;
149# }}
150# }}
151.method public static {fname}()V
152 .locals 7
153 :call_{fname}_try_start
154 new-instance v6, L{farg};
155 invoke-direct {{v6}}, L{farg};-><init>()V
156
157 const/4 v0, 2
158 new-array v1,v0, [Ljava/lang/Object;
159 const/4 v0, 0
160 invoke-virtual {{v6}}, L{farg};->CalledClassName()Ljava/lang/String;
161 move-result-object v4
162 aput-object v4,v1,v0
163
164 sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
165 const-string v3, "%s calls default method on %s\\n"
166
167 invoke-virtual {{v6}}, L{farg};->CalledInterfaceName()Ljava/lang/String;
168 move-result-object v4
169 const/4 v0, 1
170 aput-object v4, v1, v0
171
172 invoke-virtual {{v2,v3,v1}}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
173 return-void
174 :call_{fname}_try_end
175 .catch Ljava/lang/Error; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start
176 :error_{fname}_start
177 move-exception v3
178 sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
179 invoke-virtual {{v3,v2}}, Ljava/lang/Error;->printStackTrace(Ljava/io/PrintStream;)V
180 return-void
181.end method
182"""
183
184 def __init__(self, farg):
185 """
186 Initialize a test function for the given argument
187 """
188 self.farg = farg
189
190 def get_expected(self):
191 """
192 Get the expected output calling this function.
193 """
194 return "{tree} calls default method on {iface_tree}".format(
195 tree = self.farg.get_tree(), iface_tree = self.farg.get_called().get_tree())
196
197 def get_name(self):
198 """
199 Get the name of this function
200 """
201 return "TEST_FUNC_{}".format(self.farg.get_name())
202
203 def __str__(self):
204 """
205 Print the smali code of this function.
206 """
207 return self.TEST_FUNCTION_TEMPLATE.format(fname=self.get_name(), farg=self.farg.get_name())
208
209class TestClass(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin):
210 """
211 A class that will be instantiated to test default method resolution order.
212 """
213
214 TEST_CLASS_TEMPLATE = """{copyright}
215
216.class public L{class_name};
217.super Ljava/lang/Object;
218.implements L{iface_name};
219
220# public class {class_name} implements {iface_name} {{
221# public String CalledClassName() {{
222# return "{tree}";
223# }}
224# }}
225
226.method public constructor <init>()V
227 .registers 1
228 invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V
229 return-void
230.end method
231
232.method public CalledClassName()Ljava/lang/String;
233 .locals 1
234 const-string v0, "{tree}"
235 return-object v0
236.end method
237"""
238
239 def __init__(self, iface):
240 """
241 Initialize this test class which implements the given interface
242 """
243 self.iface = iface
244 self.class_name = "CLASS_"+gensym()
245
246 def get_name(self):
247 """
248 Get the name of this class
249 """
250 return self.class_name
251
252 def get_tree(self):
253 """
254 Print out a representation of the type tree of this class
255 """
256 return "[{class_name} {iface_tree}]".format(class_name = self.class_name,
257 iface_tree = self.iface.get_tree())
258
259 def __iter__(self):
260 """
261 Step through all interfaces implemented transitively by this class
262 """
263 yield self.iface
264 yield from self.iface
265
266 def get_called(self):
267 """
268 Get the interface whose default method would be called when calling the
269 CalledInterfaceName function.
270 """
271 all_ifaces = set(iface for iface in self if iface.default)
272 for i in all_ifaces:
273 if all(map(lambda j: i not in j.get_super_types(), all_ifaces)):
274 return i
275 raise Exception("UNREACHABLE! Unable to find default method!")
276
277 def __str__(self):
278 """
279 Print the smali code of this class.
280 """
281 return self.TEST_CLASS_TEMPLATE.format(copyright = get_copyright('smali'),
282 iface_name = self.iface.get_name(),
283 tree = self.get_tree(),
284 class_name = self.class_name)
285
286class TestInterface(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin):
287 """
288 An interface that will be used to test default method resolution order.
289 """
290
291 TEST_INTERFACE_TEMPLATE = """{copyright}
292.class public abstract interface L{class_name};
293.super Ljava/lang/Object;
294{implements_spec}
295
296# public interface {class_name} {extends} {ifaces} {{
297# public String CalledClassName();
298.method public abstract CalledClassName()Ljava/lang/String;
299.end method
300
301{funcs}
302
303# }}
304"""
305
306 DEFAULT_FUNC_TEMPLATE = """
307# public default String CalledInterfaceName() {{
308# return "{tree}";
309# }}
310.method public CalledInterfaceName()Ljava/lang/String;
311 .locals 1
312 const-string v0, "{tree}"
313 return-object v0
314.end method
315"""
316
317 IMPLEMENTS_TEMPLATE = """
318.implements L{iface_name};
319"""
320
321 def __init__(self, ifaces, default):
322 """
323 Initialize interface with the given super-interfaces
324 """
325 self.ifaces = sorted(ifaces)
326 self.default = default
327 end = "_DEFAULT" if default else ""
328 self.class_name = "INTERFACE_"+gensym()+end
329
330 def get_super_types(self):
331 """
332 Returns a set of all the supertypes of this interface
333 """
334 return set(i2 for i2 in self)
335
336 def get_name(self):
337 """
338 Get the name of this class
339 """
340 return self.class_name
341
342 def get_tree(self):
343 """
344 Print out a representation of the type tree of this class
345 """
346 return "[{class_name} {iftree}]".format(class_name = self.get_name(),
347 iftree = print_tree(self.ifaces))
348
349 def __iter__(self):
350 """
351 Performs depth-first traversal of the interface tree this interface is the
352 root of. Does not filter out repeats.
353 """
354 for i in self.ifaces:
355 yield i
356 yield from i
357
358 def __str__(self):
359 """
360 Print the smali code of this interface.
361 """
362 s_ifaces = " "
363 j_ifaces = " "
364 for i in self.ifaces:
365 s_ifaces += self.IMPLEMENTS_TEMPLATE.format(iface_name = i.get_name())
366 j_ifaces += " {},".format(i.get_name())
367 j_ifaces = j_ifaces[0:-1]
368 if self.default:
369 funcs = self.DEFAULT_FUNC_TEMPLATE.format(ifaces = j_ifaces,
370 tree = self.get_tree(),
371 class_name = self.class_name)
372 else:
373 funcs = ""
374 return self.TEST_INTERFACE_TEMPLATE.format(copyright = get_copyright('smali'),
375 implements_spec = s_ifaces,
376 extends = "extends" if len(self.ifaces) else "",
377 ifaces = j_ifaces,
378 funcs = funcs,
379 tree = self.get_tree(),
380 class_name = self.class_name)
381
382def print_tree(ifaces):
383 """
384 Prints a list of iface trees
385 """
386 return " ".join(i.get_tree() for i in ifaces)
387
388# The deduplicated output of subtree_sizes for each size up to
389# MAX_LEAF_IFACE_PER_OBJECT.
390SUBTREES = [set(tuple(sorted(l)) for l in subtree_sizes(i))
391 for i in range(MAX_IFACE_DEPTH + 1)]
392
393def create_interface_trees():
394 """
395 Return all legal interface trees
396 """
397 def dump_supers(s):
398 """
399 Does depth first traversal of all the interfaces in the list.
400 """
401 for i in s:
402 yield i
403 yield from i
404
405 def create_interface_trees_inner(num, allow_default):
406 for split in SUBTREES[num]:
407 ifaces = []
408 for sub in split:
409 if sub == 1:
410 ifaces.append([TestInterface([], allow_default)])
411 if allow_default:
412 ifaces[-1].append(TestInterface([], False))
413 else:
414 ifaces.append(list(create_interface_trees_inner(sub, allow_default)))
415 for supers in itertools.product(*ifaces):
416 all_supers = sorted(set(dump_supers(supers)) - set(supers))
417 for i in range(len(all_supers) + 1):
418 for combo in itertools.combinations(all_supers, i):
419 yield TestInterface(list(combo) + list(supers), allow_default)
420 if allow_default:
421 for i in range(len(split)):
422 ifaces = []
423 for sub, cs in zip(split, itertools.count()):
424 if sub == 1:
425 ifaces.append([TestInterface([], i == cs)])
426 else:
427 ifaces.append(list(create_interface_trees_inner(sub, i == cs)))
428 for supers in itertools.product(*ifaces):
429 all_supers = sorted(set(dump_supers(supers)) - set(supers))
430 for i in range(len(all_supers) + 1):
431 for combo in itertools.combinations(all_supers, i):
432 yield TestInterface(list(combo) + list(supers), False)
433
434 for num in range(1, MAX_IFACE_DEPTH):
435 yield from create_interface_trees_inner(num, True)
436
437def create_all_test_files():
438 """
439 Creates all the objects representing the files in this test. They just need to
440 be dumped.
441 """
442 mc = MainClass()
443 classes = {mc}
444 for tree in create_interface_trees():
445 classes.add(tree)
446 for i in tree:
447 classes.add(i)
448 test_class = TestClass(tree)
449 mc.add_test(test_class)
450 classes.add(test_class)
451 return mc, classes
452
453def main(argv):
454 smali_dir = Path(argv[1])
455 if not smali_dir.exists() or not smali_dir.is_dir():
456 print("{} is not a valid smali dir".format(smali_dir), file=sys.stderr)
457 sys.exit(1)
458 expected_txt = Path(argv[2])
459 mainclass, all_files = create_all_test_files()
460 with expected_txt.open('w') as out:
461 print(mainclass.get_expected(), file=out)
462 for f in all_files:
463 f.dump(smali_dir)
464
465if __name__ == '__main__':
466 main(sys.argv)