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 966.
     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 functools import total_ordering
     37 import itertools
     38 import string
     39 
     40 # The max depth the tree can have.
     41 MAX_IFACE_DEPTH = 3
     42 
     43 class 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 initial_build_different(self):
    106     return False
    107 
    108   def get_name(self):
    109     """
    110     Gets the name of this class
    111     """
    112     return "Main"
    113 
    114   def __str__(self):
    115     """
    116     Print the smali code for this test.
    117     """
    118     all_tests = sorted(self.tests)
    119     test_invoke = ""
    120     test_groups = ""
    121     for t in all_tests:
    122       test_groups += str(t)
    123     for t in all_tests:
    124       test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name())
    125     main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke)
    126 
    127     return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright('smali'),
    128                                            test_groups = test_groups,
    129                                            main_func = main_func)
    130 
    131 class Func(mixins.Named, mixins.NameComparableMixin):
    132   """
    133   A function that tests the functionality of a concrete type. Should only be
    134   constructed by MainClass.add_test.
    135   """
    136 
    137   TEST_FUNCTION_TEMPLATE = """
    138 #   public static void {fname}() {{
    139 #     try {{
    140 #       {farg} v = new {farg}();
    141 #       System.out.println("Testing {tree}");
    142 #       v.testAll();
    143 #       System.out.println("Success: testing {tree}");
    144 #       return;
    145 #     }} catch (Exception e) {{
    146 #       System.out.println("Failure: testing {tree}");
    147 #       e.printStackTrace(System.out);
    148 #       return;
    149 #     }}
    150 #   }}
    151 .method public static {fname}()V
    152     .locals 7
    153     :call_{fname}_try_start
    154       sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
    155 
    156       new-instance v6, L{farg};
    157       invoke-direct {{v6}}, L{farg};-><init>()V
    158 
    159       const-string v3, "Testing {tree}"
    160       invoke-virtual {{v2, v3}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
    161 
    162       invoke-virtual {{v6}}, L{farg};->testAll()V
    163 
    164       const-string v3, "Success: testing {tree}"
    165       invoke-virtual {{v2, v3}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
    166 
    167       return-void
    168     :call_{fname}_try_end
    169     .catch Ljava/lang/Exception; {{:call_{fname}_try_start .. :call_{fname}_try_end}} :error_{fname}_start
    170     :error_{fname}_start
    171       move-exception v3
    172       sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
    173       const-string v4, "Failure: testing {tree}"
    174       invoke-virtual {{v2, v3}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
    175       invoke-virtual {{v3,v2}}, Ljava/lang/Error;->printStackTrace(Ljava/io/PrintStream;)V
    176       return-void
    177 .end method
    178 """
    179 
    180   OUTPUT_FORMAT = """
    181 Testing {tree}
    182 {test_output}
    183 Success: testing {tree}
    184 """.strip()
    185 
    186   def __init__(self, farg):
    187     """
    188     Initialize a test function for the given argument
    189     """
    190     self.farg = farg
    191 
    192   def __str__(self):
    193     """
    194     Print the smali code for this test function.
    195     """
    196     return self.TEST_FUNCTION_TEMPLATE.format(fname = self.get_name(),
    197                                               farg  = self.farg.get_name(),
    198                                               tree  = self.farg.get_tree())
    199 
    200   def get_name(self):
    201     """
    202     Gets the name of this test function
    203     """
    204     return "TEST_FUNC_{}".format(self.farg.get_name())
    205 
    206   def get_expected(self):
    207     """
    208     Get the expected output of this function.
    209     """
    210     return self.OUTPUT_FORMAT.format(
    211         tree = self.farg.get_tree(),
    212         test_output = self.farg.get_test_output().strip())
    213 
    214 class 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 String getCalledInterface() {{
    236 #     throw new Error("Should not be called");
    237 #   }}
    238 .method public getCalledInterface()V
    239   .locals 2
    240   const-string v0, "Should not be called"
    241   new-instance v1, Ljava/lang/Error;
    242   invoke-direct {{v1, v0}}, Ljava/lang/Error;-><init>(Ljava/lang/String;)V
    243   throw v1
    244 .end method
    245 
    246 #   public void testAll() {{
    247 #     boolean failed = false;
    248 #     Error exceptions = new Error("Test failures");
    249 .method public testAll()V
    250   .locals 5
    251   const/4 v0, 0
    252   const-string v1, "Test failures"
    253   new-instance v2, Ljava/lang/Error;
    254   invoke-direct {{v2, v1}}, Ljava/lang/Error;-><init>(Ljava/lang/String;)V
    255 
    256   {test_calls}
    257 
    258 #     if (failed) {{
    259   if-eqz v0, :end
    260 #       throw exceptions;
    261     throw v2
    262   :end
    263 #     }}
    264   return-void
    265 #   }}
    266 .end method
    267 
    268 {test_funcs}
    269 
    270 # }}
    271 """
    272 
    273   IMPLEMENTS_TEMPLATE = """
    274 .implements L{iface_name};
    275 """
    276 
    277   TEST_CALL_TEMPLATE = """
    278 #     try {{
    279 #       test_{iface}_super();
    280 #     }} catch (Throwable t) {{
    281 #       exceptions.addSuppressed(t);
    282 #       failed = true;
    283 #     }}
    284   :try_{iface}_start
    285     invoke-virtual {{p0}}, L{class_name};->test_{iface}_super()V
    286     goto :error_{iface}_end
    287   :try_{iface}_end
    288   .catch Ljava/lang/Throwable; {{:try_{iface}_start .. :try_{iface}_end}} :error_{iface}_start
    289   :error_{iface}_start
    290     move-exception v3
    291     invoke-virtual {{v2, v3}}, Ljava/lang/Throwable;->addSuppressed(Ljava/lang/Throwable;)V
    292     const/4 v0, 1
    293   :error_{iface}_end
    294 """
    295 
    296   TEST_FUNC_TEMPLATE = """
    297 #   public void test_{iface}_super() {{
    298 #     try {{
    299 #       System.out.println("{class_name} -> {iface}.super.getCalledInterface(): " +
    300 #                          {iface}.super.getCalledInterface());
    301 #     }} catch (NoSuchMethodError e) {{
    302 #       System.out.println("{class_name} -> {iface}.super.getCalledInterface(): NoSuchMethodError");
    303 #     }} catch (IncompatibleClassChangeError e) {{
    304 #       System.out.println("{class_name} -> {iface}.super.getCalledInterface(): IncompatibleClassChangeError");
    305 #     }} catch (Throwable t) {{
    306 #       System.out.println("{class_name} -> {iface}.super.getCalledInterface(): Unknown error occurred");
    307 #       throw t;
    308 #     }}
    309 #   }}
    310 .method public test_{iface}_super()V
    311   .locals 3
    312   sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    313   :try_start
    314     const-string v1, "{class_name} -> {iface}.super.getCalledInterface(): "
    315     invoke-super {{p0}}, L{iface};->getCalledInterface()Ljava/lang/String;
    316     move-result-object v2
    317 
    318     invoke-virtual {{v1, v2}}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
    319     move-result-object v1
    320 
    321     invoke-virtual {{v0, v1}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
    322 
    323     return-void
    324   :try_end
    325   .catch Ljava/lang/NoSuchMethodError; {{:try_start .. :try_end}} :AME_catch
    326   .catch Ljava/lang/IncompatibleClassChangeError; {{:try_start .. :try_end}} :ICCE_catch
    327   .catch Ljava/lang/Throwable; {{:try_start .. :try_end}} :throwable_catch
    328   :AME_catch
    329     const-string v1, "{class_name} -> {iface}.super.getCalledInterface(): NoSuchMethodError"
    330     invoke-virtual {{v0, v1}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
    331     return-void
    332   :ICCE_catch
    333     const-string v1, "{class_name} -> {iface}.super.getCalledInterface(): IncompatibleClassChangeError"
    334     invoke-virtual {{v0, v1}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
    335     return-void
    336   :throwable_catch
    337     move-exception v2
    338     const-string v1, "{class_name} -> {iface}.super.getCalledInterface(): Unknown error occurred"
    339     invoke-virtual {{v0, v1}}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
    340     throw v2
    341 .end method
    342 """.strip()
    343 
    344   OUTPUT_TEMPLATE = "{class_name} -> {iface}.super.getCalledInterface(): {result}"
    345 
    346   def __init__(self, ifaces, name = None):
    347     """
    348     Initialize this test class which implements the given interfaces
    349     """
    350     self.ifaces = ifaces
    351     if name is None:
    352       self.class_name = "CLASS_"+gensym()
    353     else:
    354       self.class_name = name
    355 
    356   def get_initial_build_version(self):
    357     """
    358     Returns a version of this class that can be used for the initial build (meaning no compiler
    359     checks will be triggered).
    360     """
    361     return TestClass([i.get_initial_build_version() for i in self.ifaces], self.class_name)
    362 
    363   def initial_build_different(self):
    364     return False
    365 
    366   def get_name(self):
    367     """
    368     Gets the name of this interface
    369     """
    370     return self.class_name
    371 
    372   def get_tree(self):
    373     """
    374     Print out a representation of the type tree of this class
    375     """
    376     return "[{fname} {iftree}]".format(fname = self.get_name(), iftree = print_tree(self.ifaces))
    377 
    378   def get_test_output(self):
    379     return '\n'.join(map(lambda a: self.OUTPUT_TEMPLATE.format(class_name = self.get_name(),
    380                                                                iface = a.get_name(),
    381                                                                result = a.get_output()),
    382                          self.ifaces))
    383 
    384   def __str__(self):
    385     """
    386     Print the smali code for this class.
    387     """
    388     funcs = '\n'.join(map(lambda a: self.TEST_FUNC_TEMPLATE.format(iface = a.get_name(),
    389                                                                    class_name = self.get_name()),
    390                           self.ifaces))
    391     calls = '\n'.join(map(lambda a: self.TEST_CALL_TEMPLATE.format(iface = a.get_name(),
    392                                                                    class_name = self.get_name()),
    393                           self.ifaces))
    394     s_ifaces = '\n'.join(map(lambda a: self.IMPLEMENTS_TEMPLATE.format(iface_name = a.get_name()),
    395                              self.ifaces))
    396     j_ifaces = ', '.join(map(lambda a: a.get_name(), self.ifaces))
    397     return self.TEST_CLASS_TEMPLATE.format(copyright = get_copyright('smali'),
    398                                            implements_spec = s_ifaces,
    399                                            ifaces = j_ifaces,
    400                                            class_name = self.class_name,
    401                                            test_funcs = funcs,
    402                                            test_calls = calls)
    403 
    404 class IncompatibleClassChangeErrorResult(mixins.Named):
    405   def get_name(self):
    406     return "IncompatibleClassChangeError"
    407 
    408 ICCE = IncompatibleClassChangeErrorResult()
    409 
    410 class NoSuchMethodErrorResult(mixins.Named):
    411   def get_name(self):
    412     return "NoSuchMethodError"
    413 
    414 NSME = NoSuchMethodErrorResult()
    415 
    416 class TestInterface(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.SmaliFileMixin):
    417   """
    418   An interface that will be used to test default method resolution order.
    419   """
    420 
    421   TEST_INTERFACE_TEMPLATE = """{copyright}
    422 .class public abstract interface L{class_name};
    423 .super Ljava/lang/Object;
    424 {implements_spec}
    425 
    426 # public interface {class_name} {extends} {ifaces} {{
    427 
    428 {funcs}
    429 
    430 # }}
    431 """
    432 
    433   ABSTRACT_FUNC_TEMPLATE = """
    434 """
    435 
    436   DEFAULT_FUNC_TEMPLATE = """
    437 #   public default String getCalledInterface() {{
    438 #     return "{class_name}";
    439 #   }}
    440 .method public getCalledInterface()Ljava/lang/String;
    441   .locals 1
    442   const-string v0, "{class_name}"
    443   return-object v0
    444 .end method
    445 """
    446 
    447   IMPLEMENTS_TEMPLATE = """
    448 .implements L{iface_name};
    449 """
    450 
    451   def __init__(self, ifaces, default, name = None):
    452     """
    453     Initialize interface with the given super-interfaces
    454     """
    455     self.ifaces = ifaces
    456     self.default = default
    457     if name is None:
    458       end = "_DEFAULT" if default else ""
    459       self.class_name = "INTERFACE_"+gensym()+end
    460     else:
    461       self.class_name = name
    462 
    463   def get_initial_build_version(self):
    464     """
    465     Returns a version of this class that can be used for the initial build (meaning no compiler
    466     checks will be triggered).
    467     """
    468     return TestInterface([i.get_initial_build_version() for i in self.ifaces],
    469                          True,
    470                          self.class_name)
    471 
    472   def initial_build_different(self):
    473     return not self.default
    474 
    475   def get_name(self):
    476     """
    477     Gets the name of this interface
    478     """
    479     return self.class_name
    480 
    481   def __iter__(self):
    482     """
    483     Performs depth-first traversal of the interface tree this interface is the
    484     root of. Does not filter out repeats.
    485     """
    486     for i in self.ifaces:
    487       yield i
    488       yield from i
    489 
    490   def get_called(self):
    491     """
    492     Get the interface whose default method would be called when calling the
    493     CalledInterfaceName function.
    494     """
    495     all_ifaces = set(iface for iface in self if iface.default)
    496     for i in all_ifaces:
    497       if all(map(lambda j: i not in j.get_super_types(), all_ifaces)):
    498         return i
    499     return ICCE if any(map(lambda i: i.default, all_ifaces)) else NSME
    500 
    501   def get_super_types(self):
    502     """
    503     Returns a set of all the supertypes of this interface
    504     """
    505     return set(i2 for i2 in self)
    506 
    507   def get_output(self):
    508     if self.default:
    509       return self.get_name()
    510     else:
    511       return self.get_called().get_name()
    512 
    513   def get_tree(self):
    514     """
    515     Print out a representation of the type tree of this class
    516     """
    517     return "[{class_name} {iftree}]".format(class_name = self.get_name(),
    518                                             iftree = print_tree(self.ifaces))
    519   def __str__(self):
    520     """
    521     Print the smali code for this interface.
    522     """
    523     s_ifaces = '\n'.join(map(lambda a: self.IMPLEMENTS_TEMPLATE.format(iface_name = a.get_name()),
    524                              self.ifaces))
    525     j_ifaces = ', '.join(map(lambda a: a.get_name(), self.ifaces))
    526     if self.default:
    527       funcs = self.DEFAULT_FUNC_TEMPLATE.format(class_name = self.class_name)
    528     else:
    529       funcs = self.ABSTRACT_FUNC_TEMPLATE
    530     return self.TEST_INTERFACE_TEMPLATE.format(copyright = get_copyright('smali'),
    531                                                implements_spec = s_ifaces,
    532                                                extends = "extends" if len(self.ifaces) else "",
    533                                                ifaces = j_ifaces,
    534                                                funcs = funcs,
    535                                                class_name = self.class_name)
    536 
    537 def dump_tree(ifaces):
    538   """
    539   Yields all the interfaces transitively implemented by the set in
    540   reverse-depth-first order
    541   """
    542   for i in ifaces:
    543     yield from dump_tree(i.ifaces)
    544     yield i
    545 
    546 def print_tree(ifaces):
    547   """
    548   Prints the tree for the given ifaces.
    549   """
    550   return " ".join(i.get_tree() for i in  ifaces)
    551 
    552 # Cached output of subtree_sizes for speed of access.
    553 SUBTREES = [set(tuple(sorted(l)) for l in subtree_sizes(i)) for i in range(MAX_IFACE_DEPTH + 1)]
    554 
    555 def create_test_classes():
    556   """
    557   Yield all the test classes with the different interface trees
    558   """
    559   for num in range(1, MAX_IFACE_DEPTH + 1):
    560     for split in SUBTREES[num]:
    561       ifaces = []
    562       for sub in split:
    563         ifaces.append(list(create_interface_trees(sub)))
    564     for supers in itertools.product(*ifaces):
    565       yield TestClass(supers)
    566 
    567 def create_interface_trees(num):
    568   """
    569   Yield all the interface trees up to 'num' depth.
    570   """
    571   if num == 0:
    572     yield TestInterface(tuple(), False)
    573     yield TestInterface(tuple(), True)
    574     return
    575   for split in SUBTREES[num]:
    576     ifaces = []
    577     for sub in split:
    578       ifaces.append(list(create_interface_trees(sub)))
    579     for supers in itertools.product(*ifaces):
    580       yield TestInterface(supers, False)
    581       yield TestInterface(supers, True)
    582       for selected in (set(dump_tree(supers)) - set(supers)):
    583          yield TestInterface(tuple([selected] + list(supers)), True)
    584          yield TestInterface(tuple([selected] + list(supers)), False)
    585       # TODO Should add on some from higher up the tree.
    586 
    587 def create_all_test_files():
    588   """
    589   Creates all the objects representing the files in this test. They just need to
    590   be dumped.
    591   """
    592   mc = MainClass()
    593   classes = {mc}
    594   for clazz in create_test_classes():
    595     classes.add(clazz)
    596     for i in dump_tree(clazz.ifaces):
    597       classes.add(i)
    598     mc.add_test(clazz)
    599   return mc, classes
    600 
    601 def main(argv):
    602   smali_dir = Path(argv[1])
    603   if not smali_dir.exists() or not smali_dir.is_dir():
    604     print("{} is not a valid smali dir".format(smali_dir), file=sys.stderr)
    605     sys.exit(1)
    606   expected_txt = Path(argv[2])
    607   mainclass, all_files = create_all_test_files()
    608   with expected_txt.open('w') as out:
    609     print(mainclass.get_expected(), file=out)
    610   for f in all_files:
    611     f.dump(smali_dir)
    612 
    613 if __name__ == '__main__':
    614   main(sys.argv)
    615