blob: 9855bcf854ff61a344920bfe9b8821393f1b64b1 [file] [log] [blame]
Alex Light9139e002015-10-09 15:59:48 -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 967.
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 enum import Enum
37from functools import total_ordering
38import itertools
39import string
40
41# The max depth the type tree can have.
42MAX_IFACE_DEPTH = 3
43
44class MainClass(mixins.DumpMixin, mixins.Named, mixins.SmaliFileMixin):
45 """
46 A Main.smali file containing the Main class and the main function. It will run
47 all the test functions we have.
48 """
49
50 MAIN_CLASS_TEMPLATE = """{copyright}
51
52.class public LMain;
53.super Ljava/lang/Object;
54
55# class Main {{
56
57.method public constructor <init>()V
58 .registers 1
59 invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V
60 return-void
61.end method
62
63{test_funcs}
64
65{main_func}
66
67# }}
68"""
69
70 MAIN_FUNCTION_TEMPLATE = """
71# public static void main(String[] args) {{
72.method public static main([Ljava/lang/String;)V
73 .locals 0
74
75 {test_group_invoke}
76
77 return-void
78.end method
79# }}
80"""
81
82 TEST_GROUP_INVOKE_TEMPLATE = """
83# {test_name}();
84 invoke-static {{}}, {test_name}()V
85"""
86
87 def __init__(self):
88 """
89 Initialize this MainClass. We start out with no tests.
90 """
91 self.tests = set()
92
93 def get_expected(self):
94 """
95 Get the expected output of this test.
96 """
97 all_tests = sorted(self.tests)
98 return filter_blanks("\n".join(a.get_expected() for a in all_tests))
99
100 def add_test(self, ty):
101 """
102 Add a test for the concrete type 'ty'
103 """
104 self.tests.add(Func(ty))
105
106 def get_name(self):
107 """
108 Get the name of this class
109 """
110 return "Main"
111
112 def __str__(self):
113 """
114 Print the MainClass smali code.
115 """
116 all_tests = sorted(self.tests)
117 test_invoke = ""
118 test_funcs = ""
119 for t in all_tests:
120 test_funcs += str(t)
121 for t in all_tests:
122 test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name())
123 main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke)
124
125 return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright("smali"),
126 test_funcs = test_funcs,
127 main_func = main_func)
128
129class Func(mixins.Named, mixins.NameComparableMixin):
130 """
131 A function that tests the functionality of a concrete type. Should only be
132 constructed by MainClass.add_test.
133 """
134
135 TEST_FUNCTION_TEMPLATE = """
136# public static void {fname}() {{
137# {farg} v = null;
138# try {{
139# v = new {farg}();
140# }} catch (Throwable e) {{
141# System.out.println("Unexpected error occurred which creating {farg} instance");
142# e.printStackTrace(System.out);
143# return;
144# }}
145# try {{
146# System.out.printf("{tree} calls %s\\n", v.getName());
147# return;
148# }} catch (AbstractMethodError e) {{
149# System.out.println("{tree} threw AbstractMethodError");
150# }} catch (NoSuchMethodError e) {{
151# System.out.println("{tree} threw NoSuchMethodError");
152# }} catch (IncompatibleClassChangeError e) {{
153# System.out.println("{tree} threw IncompatibleClassChangeError");
154# }} catch (Throwable e) {{
155# e.printStackTrace(System.out);
156# return;
157# }}
158# }}
159.method public static {fname}()V
160 .locals 7
161 sget-object v4, Ljava/lang/System;->out:Ljava/io/PrintStream;
162
163 :new_{fname}_try_start
164 new-instance v0, L{farg};
165 invoke-direct {{v0}}, L{farg};-><init>()V
166 goto :call_{fname}_try_start
167 :new_{fname}_try_end
168 .catch Ljava/lang/Throwable; {{:new_{fname}_try_start .. :new_{fname}_try_end}} :new_error_{fname}_start
169 :new_error_{fname}_start
170 move-exception v6
171 const-string v5, "Unexpected error occurred which creating {farg} instance"
172 invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
173 invoke-virtual {{v6,v4}}, Ljava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V
174 return-void
175 :call_{fname}_try_start
176 const/4 v1, 1
177 new-array v2,v1, [Ljava/lang/Object;
178 const/4 v1, 0
179 invoke-virtual {{v0}}, L{farg};->getName()Ljava/lang/String;
180 move-result-object v3
181 aput-object v3,v2,v1
182
183 const-string v5, "{tree} calls %s\\n"
184
185 invoke-virtual {{v4,v5,v2}}, Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
186 return-void
187 :call_{fname}_try_end
188 .catch Ljava/lang/AbstractMethodError; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :AME_{fname}_start
189 .catch Ljava/lang/NoSuchMethodError; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :NSME_{fname}_start
190 .catch Ljava/lang/IncompatibleClassChangeError; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :ICCE_{fname}_start
191 .catch Ljava/lang/Throwable; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start
192 :AME_{fname}_start
193 const-string v5, "{tree} threw AbstractMethodError"
194 invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
195 return-void
196 :NSME_{fname}_start
197 const-string v5, "{tree} threw NoSuchMethodError"
198 invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
199 return-void
200 :ICCE_{fname}_start
201 const-string v5, "{tree} threw IncompatibleClassChangeError"
202 invoke-virtual {{v4,v5}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
203 return-void
204 :error_{fname}_start
205 move-exception v6
206 invoke-virtual {{v6,v4}}, Ljava/lang/Throwable;->printStackTrace(Ljava/io/PrintStream;)V
207 return-void
208.end method
209"""
210
211 NSME_RESULT_TEMPLATE = "{tree} threw NoSuchMethodError"
212 ICCE_RESULT_TEMPLATE = "{tree} threw IncompatibleClassChangeError"
213 AME_RESULT_TEMPLATE = "{tree} threw AbstractMethodError"
214 NORMAL_RESULT_TEMPLATE = "{tree} calls {result}"
215
216 def __init__(self, farg):
217 """
218 Initialize a test function for the given argument
219 """
220 self.farg = farg
221
222 def get_expected(self):
223 """
224 Get the expected output calling this function.
225 """
226 exp = self.farg.get_called()
227 if exp.is_empty():
228 return self.NSME_RESULT_TEMPLATE.format(tree = self.farg.get_tree())
229 elif exp.is_abstract():
230 return self.AME_RESULT_TEMPLATE.format(tree = self.farg.get_tree())
231 elif exp.is_conflict():
232 return self.ICCE_RESULT_TEMPLATE.format(tree = self.farg.get_tree())
233 else:
234 assert exp.is_default()
235 return self.NORMAL_RESULT_TEMPLATE.format(tree = self.farg.get_tree(),
236 result = exp.get_tree())
237
238 def get_name(self):
239 """
240 Get the name of this function
241 """
242 return "TEST_FUNC_{}".format(self.farg.get_name())
243
244 def __str__(self):
245 """
246 Print the smali code of this function.
247 """
248 return self.TEST_FUNCTION_TEMPLATE.format(tree = self.farg.get_tree(),
249 fname = self.get_name(),
250 farg = self.farg.get_name())
251
252class TestClass(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin):
253 """
254 A class that will be instantiated to test default method resolution order.
255 """
256
257 TEST_CLASS_TEMPLATE = """{copyright}
258
259.class public L{class_name};
260.super Ljava/lang/Object;
261.implements L{iface_name};
262
263# public class {class_name} implements {iface_name} {{
264
265.method public constructor <init>()V
266 .registers 1
267 invoke-direct {{p0}}, Ljava/lang/Object;-><init>()V
268 return-void
269.end method
270
271{funcs}
272
273# }}
274"""
275
276 def __init__(self, iface):
277 """
278 Initialize this test class which implements the given interface
279 """
280 self.iface = iface
281 self.class_name = "CLASS_"+gensym()
282
283 def get_name(self):
284 """
285 Get the name of this class
286 """
287 return self.class_name
288
289 def get_tree(self):
290 """
291 Print out a representation of the type tree of this class
292 """
293 return "[{class_name} {iface_tree}]".format(class_name = self.class_name,
294 iface_tree = self.iface.get_tree())
295
296 def __iter__(self):
297 """
298 Step through all interfaces implemented transitively by this class
299 """
300 yield self.iface
301 yield from self.iface
302
303 def get_called(self):
304 """
305 Returns the interface that will be called when the method on this class is invoked or
306 CONFLICT_TYPE if there is no interface that will be called.
307 """
308 return self.iface.get_called()
309
310 def __str__(self):
311 """
312 Print the smali code of this class.
313 """
314 return self.TEST_CLASS_TEMPLATE.format(copyright = get_copyright('smali'),
315 iface_name = self.iface.get_name(),
316 tree = self.get_tree(),
317 class_name = self.class_name,
318 funcs = "")
319
320class InterfaceType(Enum):
321 """
322 An enumeration of all the different types of interfaces we can have.
323
324 default: It has a default method
325 abstract: It has a method declared but not defined
326 empty: It does not have the method
327 """
328 default = 0
329 abstract = 1
330 empty = 2
331
332 def get_suffix(self):
333 if self == InterfaceType.default:
334 return "_DEFAULT"
335 elif self == InterfaceType.abstract:
336 return "_ABSTRACT"
337 elif self == InterfaceType.empty:
338 return "_EMPTY"
339 else:
340 raise TypeError("Interface type had illegal value.")
341
342class ConflictInterface:
343 """
344 A singleton representing a conflict of default methods.
345 """
346
347 def is_conflict(self):
348 """
349 Returns true if this is a conflict interface and calling the method on this interface will
350 result in an IncompatibleClassChangeError.
351 """
352 return True
353
354 def is_abstract(self):
355 """
356 Returns true if this is an abstract interface and calling the method on this interface will
357 result in an AbstractMethodError.
358 """
359 return False
360
361 def is_empty(self):
362 """
363 Returns true if this is an abstract interface and calling the method on this interface will
364 result in a NoSuchMethodError.
365 """
366 return False
367
368 def is_default(self):
369 """
370 Returns true if this is a default interface and calling the method on this interface will
371 result in a method actually being called.
372 """
373 return False
374
375CONFLICT_TYPE = ConflictInterface()
376
377class TestInterface(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin):
378 """
379 An interface that will be used to test default method resolution order.
380 """
381
382 TEST_INTERFACE_TEMPLATE = """{copyright}
383.class public abstract interface L{class_name};
384.super Ljava/lang/Object;
385{implements_spec}
386
387# public interface {class_name} {extends} {ifaces} {{
388
389{funcs}
390
391# }}
392"""
393
394 DEFAULT_FUNC_TEMPLATE = """
395# public default String getName() {{
396# return "{tree}";
397# }}
398.method public getName()Ljava/lang/String;
399 .locals 1
400 const-string v0, "{tree}"
401 return-object v0
402.end method
403"""
404
405 ABSTRACT_FUNC_TEMPLATE = """
406# public String getName();
407.method public abstract getName()Ljava/lang/String;
408.end method
409"""
410
411 EMPTY_FUNC_TEMPLATE = """"""
412
413 IMPLEMENTS_TEMPLATE = """
414.implements L{iface_name};
415"""
416
417 def __init__(self, ifaces, iface_type, full_name = None):
418 """
419 Initialize interface with the given super-interfaces
420 """
421 self.ifaces = sorted(ifaces)
422 self.iface_type = iface_type
423 if full_name is None:
424 end = self.iface_type.get_suffix()
425 self.class_name = "INTERFACE_"+gensym()+end
426 else:
427 self.class_name = full_name
428
429 def get_specific_version(self, v):
430 """
431 Returns a copy of this interface of the given type for use in partial compilation.
432 """
433 return TestInterface(self.ifaces, v, full_name = self.class_name)
434
435 def get_super_types(self):
436 """
437 Returns a set of all the supertypes of this interface
438 """
439 return set(i2 for i2 in self)
440
441 def is_conflict(self):
442 """
443 Returns true if this is a conflict interface and calling the method on this interface will
444 result in an IncompatibleClassChangeError.
445 """
446 return False
447
448 def is_abstract(self):
449 """
450 Returns true if this is an abstract interface and calling the method on this interface will
451 result in an AbstractMethodError.
452 """
453 return self.iface_type == InterfaceType.abstract
454
455 def is_empty(self):
456 """
457 Returns true if this is an abstract interface and calling the method on this interface will
458 result in a NoSuchMethodError.
459 """
460 return self.iface_type == InterfaceType.empty
461
462 def is_default(self):
463 """
464 Returns true if this is a default interface and calling the method on this interface will
465 result in a method actually being called.
466 """
467 return self.iface_type == InterfaceType.default
468
469 def get_called(self):
470 """
471 Returns the interface that will be called when the method on this class is invoked or
472 CONFLICT_TYPE if there is no interface that will be called.
473 """
474 if not self.is_empty() or len(self.ifaces) == 0:
475 return self
476 else:
477 best = self
478 for super_iface in self.ifaces:
479 super_best = super_iface.get_called()
480 if super_best.is_conflict():
481 return CONFLICT_TYPE
482 elif best.is_default():
483 if super_best.is_default():
484 return CONFLICT_TYPE
485 elif best.is_abstract():
486 if super_best.is_default():
487 best = super_best
488 else:
489 assert best.is_empty()
490 best = super_best
491 return best
492
493 def get_name(self):
494 """
495 Get the name of this class
496 """
497 return self.class_name
498
499 def get_tree(self):
500 """
501 Print out a representation of the type tree of this class
502 """
503 return "[{class_name} {iftree}]".format(class_name = self.get_name(),
504 iftree = print_tree(self.ifaces))
505
506 def __iter__(self):
507 """
508 Performs depth-first traversal of the interface tree this interface is the
509 root of. Does not filter out repeats.
510 """
511 for i in self.ifaces:
512 yield i
513 yield from i
514
515 def __str__(self):
516 """
517 Print the smali code of this interface.
518 """
519 s_ifaces = " "
520 j_ifaces = " "
521 for i in self.ifaces:
522 s_ifaces += self.IMPLEMENTS_TEMPLATE.format(iface_name = i.get_name())
523 j_ifaces += " {},".format(i.get_name())
524 j_ifaces = j_ifaces[0:-1]
525 if self.is_default():
526 funcs = self.DEFAULT_FUNC_TEMPLATE.format(tree = self.get_tree())
527 elif self.is_abstract():
528 funcs = self.ABSTRACT_FUNC_TEMPLATE.format()
529 else:
530 funcs = ""
531 return self.TEST_INTERFACE_TEMPLATE.format(copyright = get_copyright('smali'),
532 implements_spec = s_ifaces,
533 extends = "extends" if len(self.ifaces) else "",
534 ifaces = j_ifaces,
535 funcs = funcs,
536 tree = self.get_tree(),
537 class_name = self.class_name)
538
539def print_tree(ifaces):
540 """
541 Prints a list of iface trees
542 """
543 return " ".join(i.get_tree() for i in ifaces)
544
545# The deduplicated output of subtree_sizes for each size up to
546# MAX_LEAF_IFACE_PER_OBJECT.
547SUBTREES = [set(tuple(sorted(l)) for l in subtree_sizes(i))
548 for i in range(MAX_IFACE_DEPTH + 1)]
549
550def create_test_classes():
551 """
552 Yield all the test classes with the different interface trees
553 """
554 for num in range(1, MAX_IFACE_DEPTH + 1):
555 for iface in create_interface_trees(num):
556 yield TestClass(iface)
557
558def create_interface_trees(num):
559 """
560 Yield all the interface trees up to 'num' depth.
561 """
562 if num == 0:
563 for iftype in InterfaceType:
564 yield TestInterface(tuple(), iftype)
565 return
566 for split in SUBTREES[num]:
567 ifaces = []
568 for sub in split:
569 ifaces.append(list(create_interface_trees(sub)))
570 yield TestInterface(tuple(), InterfaceType.default)
571 for supers in itertools.product(*ifaces):
572 for iftype in InterfaceType:
573 if iftype == InterfaceType.default:
574 # We can just stop at defaults. We have other tests that a default can override an
575 # abstract and this cuts down on the number of cases significantly, improving speed of
576 # this test.
577 continue
578 yield TestInterface(supers, iftype)
579
580def create_all_test_files():
581 """
582 Creates all the objects representing the files in this test. They just need to
583 be dumped.
584 """
585 mc = MainClass()
586 classes = {mc}
587 for clazz in create_test_classes():
588 classes.add(clazz)
589 for i in clazz:
590 classes.add(i)
591 mc.add_test(clazz)
592 return mc, classes
593
594def main(argv):
595 smali_dir = Path(argv[1])
596 if not smali_dir.exists() or not smali_dir.is_dir():
597 print("{} is not a valid smali dir".format(smali_dir), file=sys.stderr)
598 sys.exit(1)
599 expected_txt = Path(argv[2])
600 mainclass, all_files = create_all_test_files()
601 with expected_txt.open('w') as out:
602 print(mainclass.get_expected(), file=out)
603 for f in all_files:
604 f.dump(smali_dir)
605
606if __name__ == '__main__':
607 main(sys.argv)