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""" 18Generate Smali test files for test 966. 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 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 131class 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 = """ 181Testing {tree} 182{test_output} 183Success: 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 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 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 404class IncompatibleClassChangeErrorResult(mixins.Named): 405 def get_name(self): 406 return "IncompatibleClassChangeError" 407 408ICCE = IncompatibleClassChangeErrorResult() 409 410class NoSuchMethodErrorResult(mixins.Named): 411 def get_name(self): 412 return "NoSuchMethodError" 413 414NSME = NoSuchMethodErrorResult() 415 416class 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 537def 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 546def 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. 553SUBTREES = [set(tuple(sorted(l)) for l in subtree_sizes(i)) for i in range(MAX_IFACE_DEPTH + 1)] 554 555def 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 567def 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 587def 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 601def 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 613if __name__ == '__main__': 614 main(sys.argv) 615