blob: 3c138abf2664e4801aa4e8f087e6a4d4e7e6b04e [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 964.
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 tree can have.
41MAX_IFACE_DEPTH = 3
42
43class MainClass(mixins.DumpMixin, mixins.Named, mixins.SmaliFileMixin):
44 """
45 A Main.smali file containing the Main class and the main function. It will run
46 all the test functions we have.
47 """
48
49 MAIN_CLASS_TEMPLATE = """{copyright}
50
51.class public LMain;
52.super Ljava/lang/Object;
53
54# class Main {{
55
56.method public constructor <init>()V
57 .registers 1
58 invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V
59 return-void
60.end method
61
62{test_groups}
63
64{main_func}
65
66# }}
67"""
68
69 MAIN_FUNCTION_TEMPLATE = """
70# public static void main(String[] args) {{
71.method public static main([Ljava/lang/String;)V
72 .locals 2
73
74 {test_group_invoke}
75
76 return-void
77.end method
78# }}
79"""
80
81 TEST_GROUP_INVOKE_TEMPLATE = """
82# {test_name}();
83 invoke-static {{}}, {test_name}()V
84"""
85
86 def __init__(self):
87 """
88 Initialize this MainClass. We start out with no tests.
89 """
90 self.tests = set()
91
92 def add_test(self, ty):
93 """
94 Add a test for the concrete type 'ty'
95 """
96 self.tests.add(Func(ty))
97
98 def get_expected(self):
99 """
100 Get the expected output of this test.
101 """
102 all_tests = sorted(self.tests)
103 return filter_blanks("\n".join(a.get_expected() for a in all_tests))
104
105 def get_name(self):
106 """
107 Gets the name of this class
108 """
109 return "Main"
110
111 def __str__(self):
112 """
113 Print the smali code for this test.
114 """
115 all_tests = sorted(self.tests)
116 test_invoke = ""
117 test_groups = ""
118 for t in all_tests:
119 test_groups += str(t)
120 for t in all_tests:
121 test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name())
122 main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke)
123
124 return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright('smali'),
125 test_groups = test_groups,
126 main_func = main_func)
127
128class Func(mixins.Named, mixins.NameComparableMixin):
129 """
130 A function that tests the functionality of a concrete type. Should only be
131 constructed by MainClass.add_test.
132 """
133
134 TEST_FUNCTION_TEMPLATE = """
135# public static void {fname}() {{
136# try {{
137# System.out.println("About to initialize {tree}");
138# {farg} v = new {farg}();
139# System.out.println("Initialized {tree}");
140# v.touchAll();
141# System.out.println("All of {tree} hierarchy initialized");
142# return;
143# }} catch (Error e) {{
144# e.printStackTrace(System.out);
145# return;
146# }}
147# }}
148.method public static {fname}()V
149 .locals 7
150 :call_{fname}_try_start
151 sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
152 const-string v3, "About to initialize {tree}"
153 invoke-virtual {{v2, v3}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
154
155 new-instance v6, L{farg};
156 invoke-direct {{v6}}, L{farg};-><init>()V
157
158 const-string v3, "Initialized {tree}"
159 invoke-virtual {{v2, v3}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
160
161 invoke-virtual {{v6}}, L{farg};->touchAll()V
162
163 const-string v3, "All of {tree} hierarchy initialized"
164 invoke-virtual {{v2, v3}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
165
166 return-void
167 :call_{fname}_try_end
168 .catch Ljava/lang/Error; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start
169 :error_{fname}_start
170 move-exception v3
171 sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
172 invoke-virtual {{v3,v2}}, Ljava/lang/Error;->printStackTrace(Ljava/io/PrintStream;)V
173 return-void
174.end method
175"""
176
177 OUTPUT_FORMAT = """
178About to initialize {tree}
179{initialize_output}
180Initialized {tree}
181{touch_output}
182All of {tree} hierarchy initialized
183""".strip()
184
185 def __init__(self, farg):
186 """
187 Initialize a test function for the given argument
188 """
189 self.farg = farg
190
191 def __str__(self):
192 """
193 Print the smali code for this test function.
194 """
195 return self.TEST_FUNCTION_TEMPLATE.format(fname=self.get_name(),
196 farg=self.farg.get_name(),
197 tree = self.farg.get_tree())
198
199 def get_name(self):
200 """
201 Gets the name of this test function
202 """
203 return "TEST_FUNC_{}".format(self.farg.get_name())
204
205 def get_expected(self):
206 """
207 Get the expected output of this function.
208 """
209 return self.OUTPUT_FORMAT.format(
210 tree = self.farg.get_tree(),
211 initialize_output = self.farg.get_initialize_output().strip(),
212 touch_output = self.farg.get_touch_output().strip())
213
214class TestClass(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin):
215 """
216 A class that will be instantiated to test interface initialization order.
217 """
218
219 TEST_CLASS_TEMPLATE = """{copyright}
220
221.class public L{class_name};
222.super Ljava/lang/Object;
223{implements_spec}
224
225# public class {class_name} implements {ifaces} {{
226#
227# public {class_name}() {{
228# }}
229.method public constructor <init>()V
230 .locals 2
231 invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V
232 return-void
233.end method
234
235# public void marker() {{
236# return;
237# }}
238.method public marker()V
239 .locals 0
240 return-void
241.end method
242
243# public void touchAll() {{
244.method public touchAll()V
245 .locals 2
246 sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
247 {touch_calls}
248 return-void
249.end method
250# }}
251# }}
252"""
253
254 IMPLEMENTS_TEMPLATE = """
255.implements L{iface_name};
256"""
257
258 TOUCH_CALL_TEMPLATE = """
259# System.out.println("{class_name} touching {iface_name}");
260# {iface_name}.field.touch();
261 const-string v1, "{class_name} touching {iface_name}"
262 invoke-virtual {{v0, v1}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
263 sget-object v1, L{iface_name};->field:LDisplayer;
264 invoke-virtual {{v1}}, LDisplayer;->touch()V
265"""
266
267 TOUCH_OUTPUT_TEMPLATE = """
268{class_name} touching {iface_name}
269{touch_output}
270""".strip()
271
272 def __init__(self, ifaces):
273 """
274 Initialize this test class which implements the given interfaces
275 """
276 self.ifaces = ifaces
277 self.class_name = "CLASS_"+gensym()
278
279 def get_name(self):
280 """
281 Gets the name of this interface
282 """
283 return self.class_name
284
285 def get_tree(self):
286 """
287 Print out a representation of the type tree of this class
288 """
289 return "[{fname} {iftree}]".format(fname = self.get_name(), iftree = print_tree(self.ifaces))
290
291 def get_initialize_output(self):
292 return "\n".join(map(lambda i: i.get_initialize_output().strip(), dump_tree(self.ifaces)))
293
294 def get_touch_output(self):
295 return "\n".join(map(lambda a: self.TOUCH_OUTPUT_TEMPLATE.format(
296 class_name = self.class_name,
297 iface_name = a.get_name(),
298 touch_output = a.get_touch_output()).strip(),
299 self.get_all_interfaces()))
300
301 def get_all_interfaces(self):
302 """
303 Returns a set of all interfaces this class transitively implements
304 """
305 return sorted(set(dump_tree(self.ifaces)))
306
307 def __str__(self):
308 """
309 Print the smali code for this class.
310 """
311 s_ifaces = '\n'.join(map(lambda a: self.IMPLEMENTS_TEMPLATE.format(iface_name = a.get_name()),
312 self.ifaces))
313 j_ifaces = ', '.join(map(lambda a: a.get_name(), self.ifaces))
314 touches = '\n'.join(map(lambda a: self.TOUCH_CALL_TEMPLATE.format(class_name = self.class_name,
315 iface_name = a.get_name()),
316 self.get_all_interfaces()))
317 return self.TEST_CLASS_TEMPLATE.format(copyright = get_copyright('smali'),
318 implements_spec = s_ifaces,
319 ifaces = j_ifaces,
320 class_name = self.class_name,
321 touch_calls = touches)
322
323class TestInterface(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin):
324 """
325 An interface that will be used to test default method resolution order.
326 """
327
328 TEST_INTERFACE_TEMPLATE = """{copyright}
329.class public abstract interface L{class_name};
330.super Ljava/lang/Object;
331{implements_spec}
332
333# public interface {class_name} {extends} {ifaces} {{
334# public static final Displayer field = new Displayer("{tree}");
335.field public final static field:LDisplayer;
336
Alex Light0db36b32015-10-27 14:06:34 -0700337.method static constructor <clinit>()V
Alex Lighteb7c1442015-08-31 13:17:42 -0700338 .locals 3
339 const-string v2, "{tree}"
340 new-instance v1, LDisplayer;
341 invoke-direct {{v1, v2}}, LDisplayer;-><init>(Ljava/lang/String;)V
342 sput-object v1, L{class_name};->field:LDisplayer;
343 return-void
344.end method
345
346# public void marker();
347.method public abstract marker()V
348.end method
349
350{funcs}
351
352# }}
353"""
354
355 DEFAULT_FUNC_TEMPLATE = """
356# public default void {class_name}_DEFAULT_FUNC() {{
357# return;
358# }}
359.method public {class_name}_DEFAULT_FUNC()V
360 .locals 0
361 return-void
362.end method
363"""
364 IMPLEMENTS_TEMPLATE = """
365.implements L{iface_name};
366"""
367
368 OUTPUT_TEMPLATE = "initialization of {tree}"
369
370 def __init__(self, ifaces, default):
371 """
372 Initialize interface with the given super-interfaces
373 """
374 self.ifaces = ifaces
375 self.default = default
376 end = "_DEFAULT" if default else ""
377 self.class_name = "INTERFACE_"+gensym()+end
378 self.cloned = False
379 self.initialized = False
380
381 def clone(self):
382 """
383 Clones this interface, returning a new one with the same structure but
384 different name.
385 """
386 return TestInterface(tuple(map(lambda a: a.clone(), self.ifaces)), self.default)
387
388 def get_name(self):
389 """
390 Gets the name of this interface
391 """
392 return self.class_name
393
394 def __iter__(self):
395 """
396 Performs depth-first traversal of the interface tree this interface is the
397 root of. Does not filter out repeats.
398 """
399 for i in self.ifaces:
400 yield i
401 yield from i
402
403 def get_tree(self):
404 """
405 Print out a representation of the type tree of this class
406 """
407 return "[{class_name} {iftree}]".format(class_name = self.get_name(),
408 iftree = print_tree(self.ifaces))
409
410 def get_initialize_output(self):
411 """
412 Returns the expected output upon the class that implements this interface being initialized.
413 """
414 if self.default and not self.initialized:
415 self.initialized = True
416 return self.OUTPUT_TEMPLATE.format(tree = self.get_tree())
417 else:
418 return ""
419
420 def get_touch_output(self):
421 """
422 Returns the expected output upon this interface being touched.
423 """
424 if not self.default and not self.initialized:
425 self.initialized = True
426 return self.OUTPUT_TEMPLATE.format(tree = self.get_tree())
427 else:
428 return ""
429
430 def __str__(self):
431 """
432 Print the smali code for this interface.
433 """
434 s_ifaces = '\n'.join(map(lambda a: self.IMPLEMENTS_TEMPLATE.format(iface_name = a.get_name()),
435 self.ifaces))
436 j_ifaces = ', '.join(map(lambda a: a.get_name(), self.ifaces))
437 if self.default:
438 funcs = self.DEFAULT_FUNC_TEMPLATE.format(class_name = self.class_name)
439 else:
440 funcs = ""
441 return self.TEST_INTERFACE_TEMPLATE.format(copyright = get_copyright('smali'),
442 implements_spec = s_ifaces,
443 extends = "extends" if len(self.ifaces) else "",
444 ifaces = j_ifaces,
445 funcs = funcs,
446 tree = self.get_tree(),
447 class_name = self.class_name)
448
449def dump_tree(ifaces):
450 """
451 Yields all the interfaces transitively implemented by the set in
452 reverse-depth-first order
453 """
454 for i in ifaces:
455 yield from dump_tree(i.ifaces)
456 yield i
457
458def print_tree(ifaces):
459 """
460 Prints the tree for the given ifaces.
461 """
462 return " ".join(i.get_tree() for i in ifaces)
463
464def clone_all(l):
465 return tuple(a.clone() for a in l)
466
467# Cached output of subtree_sizes for speed of access.
468SUBTREES = [set(tuple(l) for l in subtree_sizes(i))
469 for i in range(MAX_IFACE_DEPTH + 1)]
470
471def create_test_classes():
472 """
473 Yield all the test classes with the different interface trees
474 """
475 for num in range(1, MAX_IFACE_DEPTH + 1):
476 for split in SUBTREES[num]:
477 ifaces = []
478 for sub in split:
479 ifaces.append(list(create_interface_trees(sub)))
480 for supers in itertools.product(*ifaces):
481 yield TestClass(clone_all(supers))
482 for i in range(len(set(dump_tree(supers)) - set(supers))):
483 ns = clone_all(supers)
484 selected = sorted(set(dump_tree(ns)) - set(ns))[i]
485 yield TestClass(tuple([selected] + list(ns)))
486
487def create_interface_trees(num):
488 """
489 Yield all the interface trees up to 'num' depth.
490 """
491 if num == 0:
492 yield TestInterface(tuple(), False)
493 yield TestInterface(tuple(), True)
494 return
495 for split in SUBTREES[num]:
496 ifaces = []
497 for sub in split:
498 ifaces.append(list(create_interface_trees(sub)))
499 for supers in itertools.product(*ifaces):
500 yield TestInterface(clone_all(supers), False)
501 yield TestInterface(clone_all(supers), True)
502 # TODO Should add on some from higher up the tree.
503
504def create_all_test_files():
505 """
506 Creates all the objects representing the files in this test. They just need to
507 be dumped.
508 """
509 mc = MainClass()
510 classes = {mc}
511 for clazz in create_test_classes():
512 classes.add(clazz)
513 for i in dump_tree(clazz.ifaces):
514 classes.add(i)
515 mc.add_test(clazz)
516 return mc, classes
517
518def main(argv):
519 smali_dir = Path(argv[1])
520 if not smali_dir.exists() or not smali_dir.is_dir():
521 print("{} is not a valid smali dir".format(smali_dir), file=sys.stderr)
522 sys.exit(1)
523 expected_txt = Path(argv[2])
524 mainclass, all_files = create_all_test_files()
525 with expected_txt.open('w') as out:
526 print(mainclass.get_expected(), file=out)
527 for f in all_files:
528 f.dump(smali_dir)
529
530if __name__ == '__main__':
531 main(sys.argv)