Home | History | Annotate | Download | only in util-src
      1 #!/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 """
     18 Generate Smali test files for test 967.
     19 """
     20 
     21 import os
     22 import sys
     23 from pathlib import Path
     24 
     25 BUILD_TOP = os.getenv("ANDROID_BUILD_TOP")
     26 if 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.
     31 sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python"))
     32 
     33 from testgen.utils import get_copyright, subtree_sizes, gensym, filter_blanks
     34 import testgen.mixins as mixins
     35 
     36 from enum import Enum
     37 from functools import total_ordering
     38 import itertools
     39 import string
     40 
     41 # The max depth the type tree can have.
     42 MAX_IFACE_DEPTH = 3
     43 
     44 class 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 
    129 class 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 
    252 class 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 
    320 class 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 
    342 class 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 
    375 CONFLICT_TYPE = ConflictInterface()
    376 
    377 class 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 
    539 def 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.
    547 SUBTREES = [set(tuple(sorted(l)) for l in subtree_sizes(i))
    548             for i in range(MAX_IFACE_DEPTH + 1)]
    549 
    550 def 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 
    558 def 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 
    580 def 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 
    594 def 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 
    606 if __name__ == '__main__':
    607   main(sys.argv)
    608