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