Compare commits
909 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 381afa2741 | |||
| 82d4099541 | |||
| 5f659c8de7 | |||
| e054ea6683 | |||
| 0deafb768b | |||
| cd612b452c | |||
| 009939f866 | |||
| cd006ce78e | |||
| 71bf2aa59f | |||
| 714b935474 | |||
| 2a2b83a695 | |||
| acdaa95854 | |||
| 278c5f6142 | |||
| 8ca3cd3155 | |||
| 2b7d7ce2cf | |||
| a22efc2eb6 | |||
| 804c8eff91 | |||
| aec8ebe237 | |||
| 7353790ed1 | |||
| e09e8e5823 | |||
| 92773417b3 | |||
| 12dc4fde8a | |||
| d1e5186d4a | |||
| d06ba95374 | |||
| f0e6c8ea8e | |||
| c94c204da2 | |||
| 71617a1c70 | |||
| 9f684937c6 | |||
| ff6665c716 | |||
| aa8fd3c861 | |||
| e2b42804d5 | |||
| 0f6e942c5b | |||
| c0a81978bf | |||
| b76c882210 | |||
| 14cbfbc5a4 | |||
| 9b1761f71f | |||
| 73ca2e0fa4 | |||
| 4e4c7f7d7b | |||
| 33f2c3f220 | |||
| dcca0133fb | |||
| 408201b69b | |||
| e024628d46 | |||
| 6428f29373 | |||
| cfaa6ab6df | |||
| 91ee7565ac | |||
| 1bbcac2ab3 | |||
| 60b2353afe | |||
| 50cfa4c971 | |||
| 691bf8faca | |||
| 89b4ae6a6f | |||
| 605a67932f | |||
| 1774dc74e3 | |||
| 2d641bf049 | |||
| 94a06d9b6f | |||
| a485942731 | |||
| 2c1b3b2480 | |||
| f1f7c70aee | |||
| 718caf8cb1 | |||
| 545cd4ec12 | |||
| 444ea9ec7e | |||
| 13609a5c44 | |||
| d6ad21f6f9 | |||
| 593a32a689 | |||
| 7fed5534eb | |||
| 1560284831 | |||
| 558a86739f | |||
| bfd60b733a | |||
| ae26512601 | |||
| 498c2f5256 | |||
| 459f12d61f | |||
| bcd6e537e0 | |||
| 867c3413e9 | |||
| 0f808d5c60 | |||
| f5767dd865 | |||
| 631a855bac | |||
| c616b5b03b | |||
| 3e9f4a5060 | |||
| 31434186ab | |||
| e81ba1c431 | |||
| b219ab607f | |||
| cd8307f432 | |||
| a720105208 | |||
| 34692c41f2 | |||
| 8a8b945eb8 | |||
| 99569c52ac | |||
| f696dc715b | |||
| 21b8552386 | |||
| 4b1886700d | |||
| a83ca1f85b | |||
| 65553c156c | |||
| 440357d2e8 | |||
| 5e62b9077a | |||
| 6192ced214 | |||
| ae31fee8dd | |||
| e7b00cc76e | |||
| 7d29c5d766 | |||
| 15776c4ce3 | |||
| d720179deb | |||
| 0d69e0ac97 | |||
| 09e267f8bc | |||
| 705ceca42a | |||
| 58722d372e | |||
| f482b8b95c | |||
| 21e4dee0e2 | |||
| c7a12ad75b | |||
| 7cd77ae379 | |||
| d59c99ddfe | |||
| 85760cc844 | |||
| 0692464b85 | |||
| 3968222744 | |||
| c05368d92e | |||
| 404136cd72 | |||
| b1d5ed0066 | |||
| e22474e0a7 | |||
| 45b7304630 | |||
| 692536c584 | |||
| 4e3d85887c | |||
| 87852328da | |||
| 2207cd7b52 | |||
| f3cd4e38d7 | |||
| 2dce1c0ad9 | |||
| 258ecad277 | |||
| 7f5092c0d5 | |||
| a7f315f596 | |||
| 4dc4aa122b | |||
| e3f388af11 | |||
| 83196628c9 | |||
| 315c07d4f6 | |||
| 47dadf0a43 | |||
| c62039f327 | |||
| a5ea560edc | |||
| e09e933f9c | |||
| dbd00d5a8b | |||
| 2da772df8e | |||
| 4cdad0e83e | |||
| 2f780da305 | |||
| 9d8066f4b8 | |||
| 2cc49256a9 | |||
| 79ab2e11f8 | |||
| c1f4302e62 | |||
| f66ec9168c | |||
| 37aecf72cb | |||
| 3c7be5e9be | |||
| 89dbae8f8e | |||
| 5eec8f754d | |||
| 49a82c8388 | |||
| 26bad4a1cd | |||
| fa0a38d3aa | |||
| b56fd4d29a | |||
| 4520747167 | |||
| e444ecb2c7 | |||
| 1336c47d18 | |||
| 519a74e8d2 | |||
| dea7714ef3 | |||
| 74b88b407e | |||
| 57c28c61e0 | |||
| 87320348dd | |||
| fcb70e69c1 | |||
| 4859629850 | |||
| bd0d248fd0 | |||
| c24a3edb44 | |||
| d0f197ea3d | |||
| 5502d93cd5 | |||
| 073fd76aa2 | |||
| 492a3f6928 | |||
| 1ce8fa8bdd | |||
| 1bb90233b9 | |||
| 49ce92f540 | |||
| 2107da2e1a | |||
| d98321026d | |||
| 0b6fabbc71 | |||
| bb0fad2834 | |||
| 08f9722e33 | |||
| 62ca30bbc6 | |||
| 467403362d | |||
| 265a78cd23 | |||
| a0e13d0481 | |||
| 77fc6435a0 | |||
| cd7e5bf020 | |||
| 5e7388f686 | |||
| 1047e751e6 | |||
| c598871764 | |||
| 2921c66834 | |||
| 7bbb083c36 | |||
| 531650c9f2 | |||
| f3098741c3 | |||
| 9dbffef140 | |||
| c97e504686 | |||
| 0c4b807caa | |||
| 1eca2b6cb0 | |||
| 17cbb3eab0 | |||
| c72f2a2c96 | |||
| 610f531653 | |||
| 1e9b28b369 | |||
| 6d4caca6cc | |||
| 15953f832f | |||
| d346ed0570 | |||
| df520a1134 | |||
| f90fc1d5ec | |||
| 797904afb5 | |||
| 489fbb5e42 | |||
| 9dd5a9ef89 | |||
| 02213802c5 | |||
| 8365855475 | |||
| 55eb86d2d5 | |||
| 0a9b944431 | |||
| f1e229193c | |||
| 04e309aeff | |||
| 99eb31b312 | |||
| 287275d886 | |||
| af6f8b5391 | |||
| 3b9b103c3f | |||
| 0c55ab9001 | |||
| 9c88f70740 | |||
| 9ab003df4c | |||
| 7f8d03d192 | |||
| c64ffde11f | |||
| 1568008c67 | |||
| 84211576e4 | |||
| 553f5b063f | |||
| f5d1f288d0 | |||
| a2df92dd68 | |||
| 1c6e51f8b2 | |||
| ef5da49bc0 | |||
| 7545625af4 | |||
| e3055b95f6 | |||
| 78eed8629c | |||
| cc29da8e81 | |||
| d1a6841c20 | |||
| 600842a1a6 | |||
| 8ba3e935a5 | |||
| 87504dd2cc | |||
| e4e6f37949 | |||
| 4b314e9d99 | |||
| a48ce296b8 | |||
| cf3e17c4b8 | |||
| bae36f9720 | |||
| 11db454b84 | |||
| 1b60c1d1a8 | |||
| 8321d5e380 | |||
| 64c9ce2ab0 | |||
| 08f9a90c95 | |||
| 9f06d6744e | |||
| f228a72118 | |||
| 3249a5e0bc | |||
| d1ac43de33 | |||
| 00f5e83506 | |||
| d3ecc1f640 | |||
| 902247fcdb | |||
| bd9e1096cc | |||
| db892adf34 | |||
| 1cbaad3ec9 | |||
| 401d08ea49 | |||
| ba17f7bc00 | |||
| db2b537380 | |||
| 06f26ef8f5 | |||
| a71bb7a532 | |||
| 99934b5100 | |||
| ff5f6fca3c | |||
| 3578f7d68f | |||
| 7bc01dcfa8 | |||
| bc7a748420 | |||
| c0194d025d | |||
| 19ca8a096b | |||
| cf5bfc297b | |||
| a17f9136dd | |||
| 7d07fb0b77 | |||
| 99935bada6 | |||
| be9dae57b9 | |||
| 4629043721 | |||
| 068234f0ca | |||
| ccb8ed1394 | |||
| 8d68d409eb | |||
| e842e022ba | |||
| 1e6b30343c | |||
| ddedb8d8a0 | |||
| 472aa52706 | |||
| ab97084058 | |||
| 0911b2dc2f | |||
| fd7d08cb10 | |||
| 3ae8359408 | |||
| 6b76a3c787 | |||
| 9fbf9ef667 | |||
| c8de7b97dd | |||
| b32dc17dd7 | |||
| 7c53b985cd | |||
| c8df26f227 | |||
| 3bc9671905 | |||
| 7fd959e6e3 | |||
| 24dc68652e | |||
| aad2d24c58 | |||
| 15d56abeb6 | |||
| d89ec67888 | |||
| f9f840fb9d | |||
| 8e8a2faa10 | |||
| 0c2784bb42 | |||
| c555cd0825 | |||
| 92e28326a4 | |||
| 2dbdd1f079 | |||
| fc58022d56 | |||
| ed9fe8a573 | |||
| 49e234d9f8 | |||
| a587ce88ea | |||
| a530371b6f | |||
| 0c5a83c021 | |||
| 12bb632371 | |||
| e4fc6774b1 | |||
| f57dfb3f2e | |||
| c3f7a049d8 | |||
| 3eee83c2f2 | |||
| ed8c662631 | |||
| 850df18d7c | |||
| 7f4da306c9 | |||
| 424a8ffaf4 | |||
| 8410e62531 | |||
| 533b686e0b | |||
| c6c54f90dc | |||
| 0f5fd4e48a | |||
| a7247e8a88 | |||
| c10a30346b | |||
| 436e86fdf2 | |||
| 29a137bde3 | |||
| f02a33ace3 | |||
| 9c34a3154d | |||
| ed385e8cf1 | |||
| 554e119eb9 | |||
| aad70c7199 | |||
| a051ce6cf4 | |||
| 40f19cce61 | |||
| b158858349 | |||
| d6737860bb | |||
| 123ba2baf1 | |||
| f2f8936cd1 | |||
| f0f5c26896 | |||
| 6c61ce52a3 | |||
| 1830c273c0 | |||
| 5efe4bd845 | |||
| 75a6714057 | |||
| 6339cc2088 | |||
| 98e4c4b48d | |||
| 9d5dda12be | |||
| 84b9f11120 | |||
| 2383c40105 | |||
| 305cf5379d | |||
| 9189f23e3e | |||
| 628263343b | |||
| 19cf7c9f14 | |||
| 363cd85ba6 | |||
| 7bb752715f | |||
| 9622c948c9 | |||
| baea5247f4 | |||
| 0ca2789a18 | |||
| 119709b844 | |||
| 1c914ff286 | |||
| 31a02a70a0 | |||
| 8e0df4c423 | |||
| 86a4ed7fb3 | |||
| 19c57258fe | |||
| fef3e55c55 | |||
| 6f973ca2af | |||
| 4b73d24d4b | |||
| 65818dccb1 | |||
| 7ac0b9f57c | |||
| 699f7f6716 | |||
| dae882d55c | |||
| c0a0bba5d8 | |||
| 52ba33c5a3 | |||
| 156c979842 | |||
| f846df5371 | |||
| 4a39af7cb3 | |||
| c7890f2468 | |||
| e1dfb4ee59 | |||
| 031582dd55 | |||
| 745c52e8db | |||
| cab3f5daa7 | |||
| 77cee15d64 | |||
| e7e7b664dd | |||
| db7f2cf548 | |||
| 58365a8907 | |||
| 172f7f7534 | |||
| 05e5c82c9b | |||
| 30fbf4bcfa | |||
| 9645f33c7b | |||
| 336d6ce189 | |||
| f283ef4342 | |||
| 41abbb12a0 | |||
| 89b80900f0 | |||
| f1539d2e37 | |||
| 84ef6d0049 | |||
| aa41a4d93b | |||
| 616752759b | |||
| dc004f37ee | |||
| cfbbd99bb8 | |||
| c74b7f20a5 | |||
| 9d22b3caa8 | |||
| f8039733cc | |||
| 87ca14afea | |||
| c134329ce9 | |||
| 2148d4b0f5 | |||
| 632cc3ec16 | |||
| bcfed5b362 | |||
| 4cb9f23a7d | |||
| 0aa7173e83 | |||
| b1b49e6195 | |||
| d23f4ac16a | |||
| 01da127c4e | |||
| ccb9c46005 | |||
| 01dfae4ac7 | |||
| 395cae439e | |||
| eb77aa51b2 | |||
| ac1d1a5858 | |||
| 74a72a5ce0 | |||
| a1bfdc6323 | |||
| 0720992998 | |||
| ef28875a8e | |||
| 10fb57f6fb | |||
| 7186a4a2d7 | |||
| ab4721a8b3 | |||
| 23c05bb5f6 | |||
| fe41174be8 | |||
| 513766d45b | |||
| 79ccaadaff | |||
| ecaa87e7ae | |||
| 0a08d8b653 | |||
| 7b18d3a3a8 | |||
| 058e4c9fd7 | |||
| 9d257cd115 | |||
| 1e5541175e | |||
| bae7f1b09c | |||
| e6e8f6367e | |||
| 3970fce503 | |||
| eda2272430 | |||
| 207ce6cbbe | |||
| 1d3e6ecbcf | |||
| a5a951cfa1 | |||
| a6f935ed68 | |||
| b09c7ba6b8 | |||
| ec66476ac6 | |||
| 008216d599 | |||
| 4a92275adb | |||
| 6fca311de0 | |||
| 8e279f55f1 | |||
| 2caac21b73 | |||
| c5d977baca | |||
| b5344f4577 | |||
| 0fa3842a70 | |||
| 6fc7c7a462 | |||
| 98dbd48890 | |||
| 55fc498359 | |||
| ba6dd081e9 | |||
| 7cdb0318b1 | |||
| 17d8516d3b | |||
| b78349aef7 | |||
| eb141ad12b | |||
| b446bf275c | |||
| b7109b1b2b | |||
| 3537f849ef | |||
| 9557f04fe7 | |||
| 1bb53329b5 | |||
| e026345a45 | |||
| 3492ec3517 | |||
| eb2a1734d3 | |||
| aa8a7c03c3 | |||
| 36ee994eb8 | |||
| 65544c64bf | |||
| b49acfdacf | |||
| 29d3ce15a8 | |||
| 84cb6b9569 | |||
| a848eab407 | |||
| e1f4955286 | |||
| ca21ca5d81 | |||
| 2399bfb784 | |||
| 11cee083ba | |||
| 6e66dc25c8 | |||
| 999793c944 | |||
| d111fd0680 | |||
| eed762df44 | |||
| d3dbdb24af | |||
| e585c4ec46 | |||
| 66421be942 | |||
| 9695291e37 | |||
| b65c386b6a | |||
| cd6f6b7a83 | |||
| cdaecb31df | |||
| 5169dc52dd | |||
| f72abb2867 | |||
| 2c0725390e | |||
| 5a940a3baf | |||
| 16b6345c7f | |||
| 5f0dbf856b | |||
| 2e9039da4e | |||
| 650cf31562 | |||
| 42b7843761 | |||
| d5f4266283 | |||
| 988ada3ce9 | |||
| 74562e6868 | |||
| dd2e7e879b | |||
| 365c1faf25 | |||
| 9797fe5b81 | |||
| 6d052d39ad | |||
| 2b242b9109 | |||
| 1c8741332e | |||
| a3ff03c8f3 | |||
| 3019ee5655 | |||
| 03ae3bcefa | |||
| 52deb48aac | |||
| 214866fcb9 | |||
| 7654661b77 | |||
| 51a9c741a5 | |||
| bce86d3211 | |||
| a4a8b05ef0 | |||
| 6116a75022 | |||
| 7243ab5cb6 | |||
| 43538902a3 | |||
| cf79a519d3 | |||
| d069928613 | |||
| dd13edf262 | |||
| 653bb2ac10 | |||
| cbdc2496fc | |||
| 3de04cb638 | |||
| 68d074aecf | |||
| 0df5aa80fe | |||
| 28bcad202a | |||
| 16d8d41baf | |||
| 7bd175220e | |||
| 28d348b364 | |||
| 91691fbd6a | |||
| 9856b6d3c5 | |||
| e1ca290424 | |||
| 0fa19fb0ac | |||
| ab7b6fc29f | |||
| f8acc31b0b | |||
| 4197365131 | |||
| 389caf1825 | |||
| bcadc28207 | |||
| 7e95758a6e | |||
| d44dd0de84 | |||
| 5cee498e1d | |||
| db1b027da2 | |||
| 7f4e641860 | |||
| 710245d597 | |||
| b689efcc9f | |||
| 89563b624b | |||
| 8c7140d6b8 | |||
| c892395089 | |||
| 4ce5cc8492 | |||
| 9b091b7c08 | |||
| 7b14e322d3 | |||
| 21acaa8d37 | |||
| bf42b97580 | |||
| c705f8cbff | |||
| f8c0449d4e | |||
| b28eaa1a94 | |||
| be509c7104 | |||
| 2931617202 | |||
| 82d0d622a8 | |||
| bcaca781b1 | |||
| ffedaea505 | |||
| aec986447e | |||
| b0e3cfedf4 | |||
| da41efa3db | |||
| 9e0cd2e14e | |||
| d1af751226 | |||
| d8b39c2698 | |||
| 618b014b3d | |||
| 4e990ae2b0 | |||
| 41ee57a6f7 | |||
| 7c353a6c6f | |||
| 72b2663949 | |||
| 727285e3df | |||
| a932c7c569 | |||
| 1272ae2d4d | |||
| ddaf0375dc | |||
| f60bb6b121 | |||
| fd3498add6 | |||
| 43de744c88 | |||
| 1ac2cdfc41 | |||
| 2dea6f55b5 | |||
| 76cf4f053f | |||
| eadf046b2c | |||
| e9591efd7e | |||
| fbf750f588 | |||
| 63c528dba9 | |||
| 0f27eba1b1 | |||
| a841d0ebe7 | |||
| 6a1717a624 | |||
| ee6508e93c | |||
| 5ad082627f | |||
| e0624ce986 | |||
| 995cf2ad42 | |||
| b9fffa149b | |||
| 7e8435cceb | |||
| 37071dbaf3 | |||
| 87c1231422 | |||
| d553157bb3 | |||
| 95f9ab035d | |||
| 21e11c1d47 | |||
| 6d59f77165 | |||
| 3a798cb21c | |||
| 1fc92d2a16 | |||
| 850bd96976 | |||
| 20b03aa755 | |||
| 5281eed1a5 | |||
| bedbf94b4a | |||
| 47917fd5c2 | |||
| 0abb51c87a | |||
| 557667b125 | |||
| 1d7bb43dfd | |||
| 6b3e8f083c | |||
| bc629337d6 | |||
| 58993b9799 | |||
| a3464d7184 | |||
| a8a31643f1 | |||
| df9ae295db | |||
| 8c348c935c | |||
| 3815d30fc1 | |||
| 778b9bb851 | |||
| 57dd9e6146 | |||
| 8eef4a9075 | |||
| 87f50ab513 | |||
| 2de86b6db5 | |||
| 9be62fb16e | |||
| f6f883b9d1 | |||
| 5de4d0792f | |||
| 8c43e7f7ce | |||
| 9e24a5abeb | |||
| b587b6d694 | |||
| bc3af8e64d | |||
| 7bd428cf6d | |||
| 912f3c8467 | |||
| a8febb2447 | |||
| 1b0b526822 | |||
| 6250ebdd75 | |||
| 156e4209f9 | |||
| 7492889f4e | |||
| 0c041120f6 | |||
| ecbb53aaea | |||
| ffe739b7eb | |||
| bd05be6fb6 | |||
| ff7df3818f | |||
| 39899e4edc | |||
| dc578f98e7 | |||
| d7ce41e724 | |||
| eaaeb2c843 | |||
| 0ae7c1efbf | |||
| cb13599739 | |||
| a9251de1dd | |||
| 56798e716a | |||
| 904f0a1197 | |||
| 4d3f2740ce | |||
| f9e7a29c08 | |||
| 6cb14a1c50 | |||
| ea9f933f9e | |||
| eb2e5e3da5 | |||
| 9a4e8bdb48 | |||
| fad0091d87 | |||
| b861151f63 | |||
| feeafc407a | |||
| ea1c1eb803 | |||
| b83e20b571 | |||
| 160ad64e67 | |||
| 1213ff26b4 | |||
| 3bf93f1f85 | |||
| a502581640 | |||
| 1ec041a48f | |||
| fdaf8492ef | |||
| 2433a7e89c | |||
| 6e358d3eab | |||
| 7e462e800f | |||
| 156e54c77f | |||
| 9752ec2655 | |||
| edc1e5fa84 | |||
| a959af087b | |||
| c5994f954a | |||
| 03a09debfa | |||
| 2cf6a9b691 | |||
| 5b712e8dbc | |||
| 5d7f2c706c | |||
| 22f51e1a28 | |||
| 61684ea73d | |||
| 45b37dcd10 | |||
| c0b2230b0b | |||
| 53fa8205f2 | |||
| ddbcf8bb19 | |||
| 6e50ddf5c8 | |||
| dda49f1501 | |||
| 4e2e5aa975 | |||
| 10fd3652d4 | |||
| 4d30510706 | |||
| ee74c4d870 | |||
| 45f5e0cb04 | |||
| 7d983f2847 | |||
| 3b2b5417aa | |||
| 0a0c4eac88 | |||
| d20cd43a99 | |||
| 7b4321ecee | |||
| 188bfd1a7e | |||
| 7b6825d85c | |||
| a27cb9c34e | |||
| 8445ebf107 | |||
| 6df315017c | |||
| 1931e78367 | |||
| 90692d89c5 | |||
| 4f02864e12 | |||
| 7562ec9e1a | |||
| 6d984c0407 | |||
| 3556e591b0 | |||
| 8fdb473d78 | |||
| 398cd15dcf | |||
| 5006b3e837 | |||
| 7216635d84 | |||
| 98ef7c39b7 | |||
| e039a5a9af | |||
| 412a185fa1 | |||
| 20bfe83849 | |||
| 39093130a3 | |||
| 9e9270a8b7 | |||
| 2c904c56f4 | |||
| a3b961e72f | |||
| 0e4c8df418 | |||
| 3b2d595a06 | |||
| b29223c5b6 | |||
| d805ec15b4 | |||
| d5cfdfb50d | |||
| 2e5d73a7e4 | |||
| 1c352cc81b | |||
| 9c6c18780f | |||
| cb23b65797 | |||
| 7a16814808 | |||
| 23553c9944 | |||
| 54fbe8a7c0 | |||
| ea01102f1d | |||
| 15e1e1dfab | |||
| 37ed9cd25b | |||
| 882af04027 | |||
| aa8a298ec7 | |||
| 05ab8fd868 | |||
| 1a736aadf5 | |||
| 2cea653249 | |||
| 1085238031 | |||
| 1356d91423 | |||
| fd2dc14ede | |||
| f91c5d3647 | |||
| 1f3aebf584 | |||
| b39d79a0f9 | |||
| c410914208 | |||
| a046f1caec | |||
| c25f918cc5 | |||
| 6fb1c8d3b9 | |||
| 6047a27c89 | |||
| 8446d016e4 | |||
| ab040d36d5 | |||
| a2781b5bd3 | |||
| a7903f31ac | |||
| c134837ca6 | |||
| f0a57e6714 | |||
| d9b0365c9f | |||
| 948f9456f5 | |||
| 32f94b463f | |||
| 035506496e | |||
| 477222a395 | |||
| 4bdfdfcb36 | |||
| 3167cd0817 | |||
| b52f35259b | |||
| 20bf85b14d | |||
| f02b99a1d0 | |||
| 9132ef57f1 | |||
| d42bf2d43c | |||
| 89042fbf4a | |||
| fc4dcd2db5 | |||
| 4e07d80ebc | |||
| c4a462d601 | |||
| 7fe46fb6f3 | |||
| 2cb94856fd | |||
| f53fc03c6c | |||
| 9278c51035 | |||
| ca9dc5f944 | |||
| f30cfb6166 | |||
| 9614929f77 | |||
| 8e418d4414 | |||
| 5e81bd833b | |||
| cc2ae80e7b | |||
| b921f6097d | |||
| 9679ef893b | |||
| e4fc3cebfd | |||
| 75135819cf | |||
| 072b6cce36 | |||
| 5d60f2cdf2 | |||
| c476593925 | |||
| 089467a419 | |||
| ee68e04f84 | |||
| 9cd46e74be | |||
| 5781220415 | |||
| 965fd66e0f | |||
| 7d3caa2875 | |||
| 418546a659 | |||
| d586c84b56 | |||
| 7b9e5fe99f | |||
| 648f0edc79 | |||
| 4d9d0884c3 | |||
| 19c0bbb94c | |||
| c6995c2283 | |||
| 49a263454c | |||
| 454519220f | |||
| 118fa98ca9 | |||
| 001fa639be | |||
| 009749cf8b | |||
| da94e7b1be | |||
| ea346145f6 | |||
| a01c379c95 | |||
| c9b781d5e1 | |||
| 0b49abf3f5 | |||
| e5fe4b0a99 | |||
| 7474d305fb | |||
| 4716929158 | |||
| 528ca09e8e | |||
| 233054219f | |||
| 0e2c4d4af1 | |||
| f101e9a775 | |||
| bf3863d1bf | |||
| 94e9291c40 | |||
| 459d133b5d | |||
| 773fad66bb | |||
| e250c73109 | |||
| 6870c05ffa | |||
| 199581bf74 | |||
| a9ae971602 | |||
| 913a5b5d0f | |||
| c594137c19 | |||
| fe03c85b97 | |||
| c338652045 | |||
| 1f5cdeb01b | |||
| e53a72c5f5 | |||
| fc2690888e | |||
| b4472fd7d4 | |||
| 796d02506a | |||
| 467f729f06 | |||
| 050ec8b988 | |||
| b2f41e95bf | |||
| e733c91783 | |||
| 4e982722a5 | |||
| 2b1f815c58 | |||
| 0fff1a6754 | |||
| d95d268ec5 | |||
| b4930bc40c | |||
| 5f302238ad | |||
| 7cba2c3f81 | |||
| 218c39b1ec | |||
| e915f4fcd7 | |||
| bc9164b952 | |||
| 7c34be267f | |||
| 042464438c | |||
| cf68e4722a | |||
| 7be37ff76e | |||
| 1118236075 | |||
| ef8a685621 | |||
| e4fef402c9 | |||
| 5528afa404 | |||
| e3189fae37 | |||
| 6d963b378c | |||
| 895ddfa38f | |||
| 28e334a0ba | |||
| d060f5b877 | |||
| 7b70d617e0 | |||
| 261ba4645d | |||
| 2ab7524e71 | |||
| d55969bc65 | |||
| 9976894091 | |||
| 76a0608a04 | |||
| 0d93d335a1 | |||
| ffb9788047 | |||
| 5dd82eede9 | |||
| 14b90466ef | |||
| 43592c3e49 | |||
| b46093b3cc | |||
| 2b9c092705 | |||
| bc73010d4e | |||
| 2d8d416483 | |||
| f549a0691e | |||
| 96c2fb6f54 | |||
| f6d475292c | |||
| bd4d4f49ff | |||
| 5a24eac375 | |||
| a684118dbb | |||
| a324376e60 | |||
| 04e50afaba | |||
| 69494c9212 | |||
| b2f0f02541 | |||
| 71f249113d | |||
| 1d84c00161 | |||
| 5bc7e19a28 | |||
| c46703a05d | |||
| 129a7c39af | |||
| ac3f3e8385 | |||
| bc8ad4df86 | |||
| 53ac3ec582 | |||
| d2d43711c2 | |||
| 510035b7b7 | |||
| c923d19bcc | |||
| bff9597360 | |||
| 78b39a60e8 | |||
| 932966b6b8 | |||
| 85a18e6d75 | |||
| 5d86bf9788 | |||
| 406d9878d8 | |||
| 4e6c5cb27a | |||
| a9c0185bf5 | |||
| 0111172a03 | |||
| 57541488d3 |
@@ -0,0 +1,14 @@
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "50...100"
|
||||
|
||||
status:
|
||||
project:
|
||||
default: on
|
||||
patch:
|
||||
default: on
|
||||
changes:
|
||||
default: off
|
||||
|
||||
comment: false
|
||||
@@ -0,0 +1,20 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
continuation_indent_size = 8 #IntelliJ Idea specific workaround
|
||||
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.bat]
|
||||
end_of_line = crlf
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Decompilation error
|
||||
about: Create a report to help us improve jadx decompiler
|
||||
title: "[core]"
|
||||
labels: Core, bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Checks before report**
|
||||
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
|
||||
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
||||
- search existing issues by exception message
|
||||
|
||||
**Describe error**
|
||||
- full name of method or class with error
|
||||
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
|
||||
- **IMPORTANT!** attach or provide link to apk file (double check apk version)
|
||||
|
||||
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for jadx
|
||||
title: "[feature]"
|
||||
labels: new feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
*Describe your idea:*
|
||||
@@ -0,0 +1,5 @@
|
||||
:exclamation: Please review the [guidelines for contributing](https://github.com/skylot/jadx/blob/master/CONTRIBUTING.md#Pull-Request-Process)
|
||||
|
||||
### Description
|
||||
Please describe your pull request.
|
||||
Reference issue it fixes.
|
||||
@@ -0,0 +1,10 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
+11
-3
@@ -5,23 +5,31 @@
|
||||
|
||||
# IntelliJ Idea files
|
||||
.idea/
|
||||
.run/
|
||||
out/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.attach_pid*
|
||||
*.hprof
|
||||
|
||||
**/.DS_Store
|
||||
|
||||
bin/
|
||||
target/
|
||||
build/
|
||||
classes/
|
||||
idea/
|
||||
.gradle/
|
||||
gradle.properties
|
||||
node_modules/
|
||||
|
||||
jadx-output/
|
||||
*-tmp/
|
||||
**/tmp/
|
||||
*.jobf
|
||||
|
||||
*.dex
|
||||
*.class
|
||||
*.dump
|
||||
*.log
|
||||
*.cfg
|
||||
|
||||
*.orig
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
variables:
|
||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
||||
TERM: "dumb"
|
||||
|
||||
before_script:
|
||||
- chmod +x gradlew
|
||||
|
||||
stages:
|
||||
- test
|
||||
|
||||
java-8:
|
||||
stage: test
|
||||
image: openjdk:8
|
||||
script: ./gradlew clean build dist
|
||||
|
||||
java-11:
|
||||
stage: test
|
||||
image: openjdk:11
|
||||
script: ./gradlew clean build dist
|
||||
|
||||
java-latest:
|
||||
stage: test
|
||||
image: openjdk:latest
|
||||
script: java -version && ./gradlew clean build dist
|
||||
@@ -0,0 +1,37 @@
|
||||
branches:
|
||||
- release
|
||||
|
||||
verifyConditions:
|
||||
- '@semantic-release/github'
|
||||
|
||||
prepare:
|
||||
- path: '@semantic-release/exec'
|
||||
cmd: "JADX_VERSION=${nextRelease.version} ./gradlew clean dist"
|
||||
|
||||
preset: "angular"
|
||||
|
||||
generateNotes:
|
||||
- path: '@semantic-release/release-notes-generator'
|
||||
writerOpts:
|
||||
groupBy: "type"
|
||||
commitGroupsSort:
|
||||
- "feat"
|
||||
- "perf"
|
||||
- "fix"
|
||||
commitsSort: "header"
|
||||
|
||||
publish:
|
||||
- path: '@semantic-release/exec'
|
||||
cmd: "JADX_VERSION=${nextRelease.version} BINTRAY_PACKAGE=releases bash scripts/bintray-upload.sh"
|
||||
- path: '@semantic-release/github'
|
||||
assets:
|
||||
- path: 'build/*.zip'
|
||||
- path: 'build/*.exe'
|
||||
|
||||
success:
|
||||
- path: '@semantic-release/github'
|
||||
successComment: false
|
||||
|
||||
fail:
|
||||
- path: '@semantic-release/github'
|
||||
failComment: false
|
||||
+38
-16
@@ -1,24 +1,46 @@
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk7
|
||||
- openjdk6
|
||||
os: linux
|
||||
dist: trusty
|
||||
|
||||
# don't build on tag push
|
||||
if: tag IS blank
|
||||
|
||||
git:
|
||||
depth: false
|
||||
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
- chmod +x gradlew
|
||||
|
||||
script:
|
||||
- TERM=dumb ./gradlew clean build dist
|
||||
# override install to skip 'gradle assemble'
|
||||
install: true
|
||||
|
||||
after_success:
|
||||
- TERM=dumb ./gradlew jacocoTestReport coveralls
|
||||
env:
|
||||
global:
|
||||
- TERM=dumb
|
||||
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
|
||||
|
||||
sudo: false
|
||||
jdk:
|
||||
- openjdk8
|
||||
- openjdk11
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle
|
||||
script: ./gradlew clean build
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- skylot@gmail.com
|
||||
jobs:
|
||||
include:
|
||||
- stage: checks
|
||||
jdk: openjdk11
|
||||
if: branch = master AND repo = env(MAIN_REPO) AND type = push
|
||||
script: bash scripts/travis-checks.sh
|
||||
|
||||
- stage: deploy-unstable
|
||||
jdk: openjdk8
|
||||
if: branch = master AND repo = env(MAIN_REPO) AND type = push
|
||||
script: bash scripts/travis-master.sh
|
||||
|
||||
- stage: deploy-release
|
||||
language: node_js
|
||||
jdk: openjdk8
|
||||
node_js: 11
|
||||
if: branch = release AND repo = env(MAIN_REPO) AND type = push
|
||||
script: bash scripts/travis-release.sh
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at skylot@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -0,0 +1,33 @@
|
||||
# Contributing
|
||||
|
||||
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
|
||||
|
||||
## Open Issue
|
||||
|
||||
1. Before proceed please do:
|
||||
- check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
|
||||
- check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
||||
- search existing issues by exception message
|
||||
|
||||
2. Describe error
|
||||
**Describe error**
|
||||
- full name of method or class with error
|
||||
- full java stacktrace (no need to copy method fallback code (commented pseudocode))
|
||||
- **IMPORTANT!:** attach or provide link to apk file (double check apk version)
|
||||
|
||||
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
||||
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues.
|
||||
|
||||
1. Before start working on PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected.
|
||||
|
||||
1. Use only features and API from Java 8 or below.
|
||||
|
||||
1. If possible don't add additional dependencies especially if they are big.
|
||||
|
||||
1. Make sure your code is correctly formatted, see description here: [Code Formatting](https://github.com/skylot/jadx/wiki/Code-Formatting).
|
||||
|
||||
1. Make sure your changes is passing build: `./gradlew clean build dist`
|
||||
@@ -144,7 +144,8 @@ THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Jadx-gui components
|
||||
===================
|
||||
|
||||
RSyntaxTextArea library licensed under modified BSD license:
|
||||
RSyntaxTextArea library (https://github.com/bobbylight/RSyntaxTextArea)
|
||||
licensed under modified BSD license:
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2012, Robert Futrell
|
||||
@@ -174,8 +175,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Icons copied from several places:
|
||||
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
||||
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
|
||||
Concurrent Trees (https://code.google.com/p/concurrent-trees/)
|
||||
licenced under Apache License 2.0:
|
||||
|
||||
*******************************************************************************
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Image Viewer (https://github.com/kazocsaba/imageviewer)
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2008-2012 Kazó Csaba
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*******************************************************************************
|
||||
|
||||
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
|
||||
|
||||
Icons copied from several places:
|
||||
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
||||
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed
|
||||
under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
|
||||
|
||||
@@ -1,89 +1,125 @@
|
||||
<img src="https://raw.githubusercontent.com/skylot/jadx/master/jadx-gui/src/main/resources/logos/jadx-logo.png" width="64" align="left" />
|
||||
|
||||
## JADX
|
||||
|
||||
[](https://travis-ci.org/skylot/jadx)
|
||||
[](https://drone.io/github.com/skylot/jadx/latest)
|
||||
[](https://coveralls.io/r/skylot/jadx)
|
||||
[](https://scan.coverity.com/projects/2166)
|
||||
[](https://travis-ci.com/skylot/jadx)
|
||||
[](https://codecov.io/gh/skylot/jadx)
|
||||
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
||||
[](https://sonarcloud.io/dashboard?id=jadx)
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
[](https://github.com/semantic-release/semantic-release)
|
||||
|
||||
**jadx** - Dex to Java decompiler
|
||||
|
||||
Command line and GUI tools for produce Java source code from Android Dex and Apk files
|
||||
Command line and GUI tools for producing Java source code from Android Dex and Apk files
|
||||
|
||||

|
||||
**Main features:**
|
||||
- decompile Dalvik bytecode to java classes from APK, dex, aar and zip files
|
||||
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
|
||||
- deobfuscator included
|
||||
|
||||
### Downloads
|
||||
- [unstable](https://drone.io/github.com/skylot/jadx/files)
|
||||
- from [github](https://github.com/skylot/jadx/releases)
|
||||
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
|
||||
**jadx-gui features:**
|
||||
- view decompiled code with highlighted syntax
|
||||
- jump to declaration
|
||||
- find usage
|
||||
- full text search
|
||||
|
||||
See these features in action here: [jadx-gui features overview](https://github.com/skylot/jadx/wiki/jadx-gui-features-overview)
|
||||
|
||||
|
||||
### Building from source
|
||||
git clone https://github.com/skylot/jadx.git
|
||||
cd jadx
|
||||
./gradlew dist
|
||||

|
||||
|
||||
|
||||
### Download
|
||||
- latest [unstable build:  ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
|
||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||
- release from [bintray:  ](https://bintray.com/skylot/jadx/releases/_latestVersion#files)
|
||||
|
||||
After download unpack zip file go to `bin` directory and run:
|
||||
- `jadx` - command line version
|
||||
- `jadx-gui` - UI version
|
||||
|
||||
On Windows run `.bat` files with double-click\
|
||||
**Note:** ensure you have installed Java 8 or later 64-bit version.
|
||||
For windows you can download it from [adoptopenjdk.net](https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot#x64_win) (select "Install JRE").
|
||||
|
||||
### Install
|
||||
1. Arch linux
|
||||
```bash
|
||||
sudo pacman -S jadx
|
||||
```
|
||||
2. macOS
|
||||
```bash
|
||||
brew install jadx
|
||||
```
|
||||
|
||||
### Build from source
|
||||
JDK 8 or higher must be installed:
|
||||
```
|
||||
git clone https://github.com/skylot/jadx.git
|
||||
cd jadx
|
||||
./gradlew dist
|
||||
```
|
||||
|
||||
(on Windows, use `gradlew.bat` instead of `./gradlew`)
|
||||
|
||||
Scripts for run jadx will be placed in `build/jadx/bin`
|
||||
and also packed to `build/jadx-<version>.zip`
|
||||
|
||||
|
||||
### Run
|
||||
Run **jadx** on itself:
|
||||
|
||||
cd build/jadx/
|
||||
bin/jadx -d out lib/jadx-core-*.jar
|
||||
#or
|
||||
bin/jadx-gui lib/jadx-core-*.jar
|
||||
|
||||
|
||||
### Usage
|
||||
```
|
||||
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
|
||||
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
|
||||
options:
|
||||
-d, --output-dir - output directory
|
||||
-j, --threads-count - processing threads count
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
-r, --no-res - do not decode resources
|
||||
-s, --no-src - do not decompile source code
|
||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||
--cfg - save methods control flow graph to dot file
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-v, --verbose - verbose output
|
||||
--deobf - activate deobfuscation
|
||||
--deobf-min - min length of name
|
||||
--deobf-max - max length of name
|
||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||
-h, --help - print this help
|
||||
-d, --output-dir - output directory
|
||||
-ds, --output-dir-src - output directory for sources
|
||||
-dr, --output-dir-res - output directory for resources
|
||||
-r, --no-res - do not decode resources
|
||||
-s, --no-src - do not decompile source code
|
||||
--single-class - decompile a single class
|
||||
--output-format - can be 'java' or 'json', default: java
|
||||
-e, --export-gradle - save as android gradle project
|
||||
-j, --threads-count - processing threads count, default: 4
|
||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||
--no-imports - disable use of imports, always write entire package name
|
||||
--no-debug-info - disable debug info
|
||||
--no-inline-anonymous - disable anonymous classes inline
|
||||
--no-replace-consts - don't replace constant value with matching constant field
|
||||
--escape-unicode - escape non latin characters in strings (with \u)
|
||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||
--deobf - activate deobfuscation
|
||||
--deobf-min - min length of name, renamed if shorter, default: 3
|
||||
--deobf-max - max length of name, renamed if longer, default: 64
|
||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' (default)
|
||||
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
||||
--cfg - save methods control flow graph to dot file
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
-v, --verbose - verbose output (set --log-level to DEBUG)
|
||||
-q, --quiet - turn off output (set --log-level to QUIET)
|
||||
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
|
||||
--version - print jadx version
|
||||
-h, --help - print this help
|
||||
Example:
|
||||
jadx -d out classes.dex
|
||||
jadx --rename-flags "none" classes.dex
|
||||
jadx --rename-flags "valid,printable" classes.dex
|
||||
jadx --log-level error app.apk
|
||||
```
|
||||
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
||||
|
||||
### Troubleshooting
|
||||
##### Out of memory error:
|
||||
- Reduce processing threads count (`-j` option)
|
||||
- Increase maximum java heap size:
|
||||
* command line (example for linux):
|
||||
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
|
||||
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
|
||||
`DEFAULT_JVM_OPTS="-Xmx2500M"`
|
||||
|
||||
|
||||
### Contribution
|
||||
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
|
||||
|
||||
### Contributing
|
||||
To support this project you can:
|
||||
- Post thoughts about new features/optimizations that important to you
|
||||
- Submit bug using one of following patterns:
|
||||
* Java code examples which decompiles incorrectly
|
||||
* Error log and link to _public available_ apk file or app page on Google play
|
||||
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
|
||||
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
|
||||
|
||||
And any other comments will be very helpfull,
|
||||
because at current stage of development it is very time consuming
|
||||
to **find** new bugs, design and implement new features.
|
||||
Also I need to **prioritize** these task for complete most important at first.
|
||||
### Related projects:
|
||||
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
|
||||
|
||||
---------------------------------------
|
||||
*Licensed under the Apache 2.0 License*
|
||||
|
||||
*Copyright 2015 by Skylot*
|
||||
|
||||
+136
-75
@@ -1,109 +1,170 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.github.kt3k.coveralls" version "2.3.1"
|
||||
id "info.solidsoft.pitest" version "1.1.4"
|
||||
// id "com.github.ben-manes.versions" version "0.8"
|
||||
id 'org.sonarqube' version '3.0'
|
||||
id 'com.github.ben-manes.versions' version '0.33.0'
|
||||
id "com.diffplug.spotless" version "5.5.1"
|
||||
}
|
||||
|
||||
apply plugin: 'sonar-runner'
|
||||
|
||||
ext.jadxVersion = file('version').readLines().get(0)
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
version = jadxVersion
|
||||
println("jadx version: ${jadxVersion}")
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'com.github.kt3k.coveralls'
|
||||
allprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
version = jadxVersion
|
||||
version = jadxVersion
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_6
|
||||
targetCompatibility = JavaVersion.VERSION_1_6
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
if (!"$it".contains(':jadx-samples:')) {
|
||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||
}
|
||||
}
|
||||
tasks.withType(JavaCompile) {
|
||||
if (!"$it".contains(':jadx-samples:')) {
|
||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
version = jadxVersion
|
||||
manifest {
|
||||
mainAttributes('jadx-version': jadxVersion)
|
||||
}
|
||||
}
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.10'
|
||||
jar {
|
||||
manifest {
|
||||
mainAttributes('jadx-version': jadxVersion)
|
||||
}
|
||||
}
|
||||
|
||||
testCompile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||
testCompile 'cglib:cglib-nodep:3.1'
|
||||
}
|
||||
dependencies {
|
||||
implementation 'org.slf4j:slf4j-api:1.7.30'
|
||||
compileOnly 'org.jetbrains:annotations:20.1.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
}
|
||||
testImplementation 'ch.qos.logback:logback-classic:1.2.3'
|
||||
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||
testImplementation 'org.mockito:mockito-core:3.5.10'
|
||||
testImplementation 'org.assertj:assertj-core:3.17.2'
|
||||
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled = true // coveralls plugin depends on xml format report
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
|
||||
|
||||
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
||||
testCompileOnly 'org.jetbrains:annotations:20.1.0'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = "0.8.6"
|
||||
}
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
checkstyleMain {
|
||||
// exclude all sources in samples module
|
||||
exclude '**/samples/**'
|
||||
}
|
||||
}
|
||||
|
||||
/* Sonar runner configuration */
|
||||
repositories {
|
||||
mavenCentral()
|
||||
sonarqube {
|
||||
properties {
|
||||
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
|
||||
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
|
||||
}
|
||||
}
|
||||
sonarRunner {
|
||||
toolVersion = '2.4'
|
||||
|
||||
spotless {
|
||||
java {
|
||||
target fileTree(rootDir).matching {
|
||||
include 'jadx-cli/src/**/java/**/*.java'
|
||||
include 'jadx-core/src/**/java/**/*.java'
|
||||
include 'jadx-gui/src/**/java/**/*.java'
|
||||
include 'jadx-plugins/**/java/**/*.java'
|
||||
}
|
||||
|
||||
importOrderFile 'config/code-formatter/eclipse.importorder'
|
||||
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
||||
removeUnusedImports()
|
||||
|
||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
format 'misc', {
|
||||
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
|
||||
targetExclude ".gradle/**", ".idea/**", "*/build/**"
|
||||
|
||||
lineEndings(com.diffplug.spotless.LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyUpdates {
|
||||
resolutionStrategy {
|
||||
componentSelection { rules ->
|
||||
rules.all { ComponentSelection selection ->
|
||||
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
|
||||
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
|
||||
}
|
||||
if (rejected) {
|
||||
selection.reject('Release candidate')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
|
||||
destinationDir file("$buildDir/jadx")
|
||||
['jadx-cli', 'jadx-gui'].each {
|
||||
from tasks.getByPath(":${it}:installDist").destinationDir
|
||||
}
|
||||
destinationDir file("$buildDir/jadx")
|
||||
['jadx-cli', 'jadx-gui'].each {
|
||||
from tasks.getByPath(":${it}:installDist").destinationDir
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
task pack(type: Zip, dependsOn: copyArtifacts) {
|
||||
destinationDir buildDir
|
||||
archiveName "jadx-${jadxVersion}.zip"
|
||||
from copyArtifacts.destinationDir
|
||||
destinationDirectory = buildDir
|
||||
archiveFileName = "jadx-${jadxVersion}.zip"
|
||||
from copyArtifacts.destinationDir
|
||||
}
|
||||
|
||||
task dist(dependsOn: pack) {
|
||||
description = 'Build jadx distribution zip'
|
||||
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
|
||||
group 'jadx'
|
||||
description = 'Copy exe to build dir'
|
||||
destinationDir buildDir
|
||||
from tasks.getByPath('jadx-gui:createExe').outputs
|
||||
include '*.exe'
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
task dist(dependsOn: [pack, copyExe]) {
|
||||
group 'jadx'
|
||||
description = 'Build jadx distribution zip'
|
||||
}
|
||||
|
||||
task samples(dependsOn: 'jadx-samples:samples') {
|
||||
}
|
||||
|
||||
task pitest(overwrite: true, dependsOn: 'jadx-core:pitest') {
|
||||
group 'jadx'
|
||||
}
|
||||
|
||||
task cleanBuildDir(type: Delete) {
|
||||
delete buildDir
|
||||
group 'jadx'
|
||||
delete buildDir
|
||||
}
|
||||
|
||||
build.dependsOn(dist, samples)
|
||||
test.dependsOn(samples)
|
||||
|
||||
clean.dependsOn(cleanBuildDir)
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '2.3'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
|
||||
"http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
|
||||
|
||||
<module name="Checker">
|
||||
<property name="fileExtensions" value="java, properties, xml"/>
|
||||
<property name="charset" value="UTF-8"/>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<property name="tabWidth" value="4"/>
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="format" value="^\t* "/>
|
||||
<property name="message" value="Indent must use tab characters"/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</module>
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="format" value="^(?!\s+\* $).*?\s+$"/>
|
||||
<property name="message" value="Line has trailing spaces."/>
|
||||
</module>
|
||||
<module name="AvoidEscapedUnicodeCharacters">
|
||||
<property name="allowEscapesForControlCharacters" value="true"/>
|
||||
<property name="allowByTailComment" value="true"/>
|
||||
<property name="allowNonPrintableEscapes" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="EmptyLineSeparator">
|
||||
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||
<property name="allowMultipleEmptyLines" value="false"/>
|
||||
</module>
|
||||
|
||||
<!-- whitespaces -->
|
||||
<module name="SingleSpaceSeparator"/>
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="OperatorWrap"/>
|
||||
<module name="ParenPad"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround">
|
||||
<property name="allowEmptyMethods" value="true"/>
|
||||
</module>
|
||||
<!-- <module name="EmptyForIteratorPad"/> -->
|
||||
<!-- <module name="NoWhitespaceAfter"/>-->
|
||||
|
||||
<module name="NoLineWrap"/>
|
||||
|
||||
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
<!-- <module name="AvoidStarImport"/> -->
|
||||
|
||||
<module name="NeedBraces"/>
|
||||
<module name="LeftCurly"/>
|
||||
<module name="RightCurly"/>
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="expected|ignore"/>
|
||||
</module>
|
||||
|
||||
<!-- naming -->
|
||||
<module name="PackageName"/>
|
||||
<module name="TypeName"/>
|
||||
<module name="InterfaceTypeParameterName"/>
|
||||
<module name="ClassTypeParameterName"/>
|
||||
<module name="StaticVariableName"/>
|
||||
<module name="ConstantName"/>
|
||||
<module name="MemberName"/>
|
||||
<module name="MethodName"/>
|
||||
<module name="MethodTypeParameterName"/>
|
||||
<module name="ParameterName"/>
|
||||
<module name="LambdaParameterName"/>
|
||||
<module name="LocalVariableName"/>
|
||||
<module name="LocalFinalVariableName"/>
|
||||
<module name="CatchParameterName"/>
|
||||
<!-- <module name="HiddenField"/> -->
|
||||
|
||||
<!-- annotations -->
|
||||
<module name="AnnotationLocation"/>
|
||||
<module name="AnnotationUseStyle">
|
||||
<property name="elementStyle" value="compact"/>
|
||||
</module>
|
||||
<module name="MissingOverride"/>
|
||||
<!-- <module name="MissingDeprecated"/> -->
|
||||
|
||||
<module name="ModifierOrder"/>
|
||||
<!-- <module name="RedundantModifier"/> -->
|
||||
<!-- <module name="ParameterNumber"/> -->
|
||||
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="DefaultComesLast"/>
|
||||
<module name="EqualsHashCode"/>
|
||||
<module name="FallThrough"/>
|
||||
<!-- <module name="IllegalCatch"/> -->
|
||||
<module name="IllegalThrows"/>
|
||||
<module name="IllegalType"/>
|
||||
<module name="InnerAssignment"/>
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="NoClone"/>
|
||||
<module name="NoFinalizer"/>
|
||||
<module name="OneStatementPerLine"/>
|
||||
<module name="PackageDeclaration"/>
|
||||
<module name="StringLiteralEquality"/>
|
||||
|
||||
<!-- design -->
|
||||
<module name="OneTopLevelClass"/>
|
||||
<module name="MutableException"/>
|
||||
<module name="InterfaceIsType"/>
|
||||
<module name="ThrowsCount">
|
||||
<property name="max" value="2"/>
|
||||
</module>
|
||||
|
||||
<!-- misc -->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="OuterTypeFilename"/>
|
||||
|
||||
<!-- sizes -->
|
||||
<module name="OuterTypeNumber"/>
|
||||
|
||||
<module name="SuppressWarningsHolder"/>
|
||||
</module>
|
||||
|
||||
<module name="NewlineAtEndOfFile"/>
|
||||
<module name="SuppressWarningsFilter"/>
|
||||
</module>
|
||||
@@ -0,0 +1,8 @@
|
||||
#Import Order
|
||||
0=java
|
||||
1=javax
|
||||
2=org
|
||||
3=com
|
||||
4=
|
||||
5=jadx
|
||||
6=\#
|
||||
@@ -0,0 +1,348 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="16">
|
||||
<profile kind="CodeFormatterProfile" name="jadx eclipse" version="16">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="100"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
@@ -0,0 +1 @@
|
||||
org.gradle.daemon=false
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +22,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,12 +36,53 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
@@ -90,7 +106,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -110,11 +126,13 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
@@ -138,27 +156,30 @@ if $cygwin ; then
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Vendored
+26
-27
@@ -1,3 +1,19 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@@ -8,20 +24,23 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -35,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -45,34 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
+23
-14
@@ -1,20 +1,29 @@
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName = 'jadx.cli.JadxCLI'
|
||||
applicationName = 'jadx'
|
||||
plugins {
|
||||
id 'application'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile(project(':jadx-core'))
|
||||
compile 'com.beust:jcommander:1.47'
|
||||
compile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
implementation(project(':jadx-core'))
|
||||
|
||||
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
|
||||
implementation 'com.beust:jcommander:1.80'
|
||||
implementation 'ch.qos.logback:logback-classic:1.2.3'
|
||||
}
|
||||
|
||||
application {
|
||||
applicationName = 'jadx'
|
||||
mainClassName = 'jadx.cli.JadxCLI'
|
||||
applicationDefaultJvmArgs = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
|
||||
}
|
||||
|
||||
applicationDistribution.with {
|
||||
into('') {
|
||||
from '../.'
|
||||
include 'README.md'
|
||||
include 'NOTICE'
|
||||
include 'LICENSE'
|
||||
}
|
||||
into('') {
|
||||
from '../.'
|
||||
include 'README.md'
|
||||
include 'NOTICE'
|
||||
include 'LICENSE'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.ParameterDescription;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.beust.jcommander.Parameterized;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
|
||||
public class JCommanderWrapper<T> {
|
||||
private final JCommander jc;
|
||||
|
||||
public JCommanderWrapper(T obj) {
|
||||
this.jc = JCommander.newBuilder().addObject(obj).build();
|
||||
}
|
||||
|
||||
public boolean parse(String[] args) {
|
||||
try {
|
||||
jc.parse(args);
|
||||
return true;
|
||||
} catch (ParameterException e) {
|
||||
System.err.println("Arguments parse error: " + e.getMessage());
|
||||
printUsage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void overrideProvided(T obj) {
|
||||
List<ParameterDescription> fieldsParams = jc.getParameters();
|
||||
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
|
||||
parameters.add(jc.getMainParameterValue());
|
||||
parameters.addAll(fieldsParams);
|
||||
for (ParameterDescription parameter : parameters) {
|
||||
if (parameter.isAssigned()) {
|
||||
// copy assigned field value to obj
|
||||
Parameterized parameterized = parameter.getParameterized();
|
||||
Object val = parameterized.get(parameter.getObject());
|
||||
parameterized.set(obj, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
// print usage in not sorted fields order (by default its sorted by description)
|
||||
PrintStream out = System.out;
|
||||
out.println();
|
||||
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
||||
out.println();
|
||||
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
|
||||
out.println("options:");
|
||||
|
||||
List<ParameterDescription> params = jc.getParameters();
|
||||
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
|
||||
int maxNamesLen = 0;
|
||||
for (ParameterDescription p : params) {
|
||||
paramsMap.put(p.getParameterized().getName(), p);
|
||||
int len = p.getNames().length();
|
||||
if (len > maxNamesLen) {
|
||||
maxNamesLen = len;
|
||||
}
|
||||
}
|
||||
|
||||
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
||||
for (Field f : getFields(args.getClass())) {
|
||||
String name = f.getName();
|
||||
ParameterDescription p = paramsMap.get(name);
|
||||
if (p == null) {
|
||||
continue;
|
||||
}
|
||||
StringBuilder opt = new StringBuilder();
|
||||
opt.append(" ").append(p.getNames());
|
||||
addSpaces(opt, maxNamesLen - opt.length() + 3);
|
||||
opt.append("- ").append(p.getDescription());
|
||||
String defaultValue = getDefaultValue(args, f, opt);
|
||||
if (defaultValue != null) {
|
||||
opt.append(", default: ").append(defaultValue);
|
||||
}
|
||||
out.println(opt);
|
||||
}
|
||||
out.println("Example:");
|
||||
out.println(" jadx -d out classes.dex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all declared fields of the specified class and all super classes
|
||||
*
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
private List<Field> getFields(Class<?> clazz) {
|
||||
List<Field> fieldList = new LinkedList<>();
|
||||
while (clazz != null) {
|
||||
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return fieldList;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
|
||||
try {
|
||||
Class<?> fieldType = f.getType();
|
||||
if (fieldType == int.class) {
|
||||
return Integer.toString(f.getInt(args));
|
||||
}
|
||||
if (fieldType == String.class) {
|
||||
return (String) f.get(args);
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(fieldType)) {
|
||||
Enum<?> val = (Enum<?>) f.get(args);
|
||||
if (val != null) {
|
||||
return val.name();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void addSpaces(StringBuilder str, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
str.append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,54 @@
|
||||
package jadx.cli;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.impl.NoOpCodeCache;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxCLI {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||
|
||||
public static void main(String[] args) throws JadxException {
|
||||
public static void main(String[] args) {
|
||||
int result = 0;
|
||||
try {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (processArgs(jadxArgs, args)) {
|
||||
processAndSave(jadxArgs);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
result = execute(args);
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
result = 1;
|
||||
} catch (Exception e) {
|
||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
||||
System.exit(1);
|
||||
result = 1;
|
||||
} finally {
|
||||
FileUtils.deleteTempRootDir();
|
||||
System.exit(result);
|
||||
}
|
||||
}
|
||||
|
||||
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
|
||||
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
|
||||
jadx.setOutputDir(jadxArgs.getOutDir());
|
||||
jadx.loadFiles(jadxArgs.getInput());
|
||||
jadx.save();
|
||||
if (jadx.getErrorsCount() != 0) {
|
||||
jadx.printErrorsReport();
|
||||
LOG.error("finished with errors");
|
||||
} else {
|
||||
LOG.info("done");
|
||||
public static int execute(String[] args) {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
return processAndSave(jadxArgs.toJadxArgs());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
|
||||
if (!jadxArgs.processArgs(args)) {
|
||||
return false;
|
||||
}
|
||||
if (jadxArgs.getInput().isEmpty()) {
|
||||
LOG.error("Please specify input file");
|
||||
jadxArgs.printUsage();
|
||||
return false;
|
||||
}
|
||||
File outputDir = jadxArgs.getOutDir();
|
||||
if (outputDir == null) {
|
||||
String outDirName;
|
||||
File file = jadxArgs.getInput().get(0);
|
||||
String name = file.getName();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
outDirName = name.substring(0, pos);
|
||||
private static int processAndSave(JadxArgs jadxArgs) {
|
||||
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||
jadx.load();
|
||||
jadx.save();
|
||||
int errorsCount = jadx.getErrorsCount();
|
||||
if (errorsCount != 0) {
|
||||
jadx.printErrorsReport();
|
||||
LOG.error("finished with errors, count: {}", errorsCount);
|
||||
} else {
|
||||
outDirName = name + "-jadx-out";
|
||||
LOG.info("done");
|
||||
}
|
||||
LOG.info("output directory: {}", outDirName);
|
||||
outputDir = new File(outDirName);
|
||||
jadxArgs.setOutputDir(outputDir);
|
||||
}
|
||||
if (outputDir.exists() && !outputDir.isDirectory()) {
|
||||
throw new JadxException("Output directory exists as file " + outputDir);
|
||||
}
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,245 +1,360 @@
|
||||
package jadx.cli;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
import com.beust.jcommander.Parameter;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
public class JadxCLIArgs {
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
|
||||
protected List<String> files = new ArrayList<>(1);
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParameterDescription;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||
protected String outDir;
|
||||
|
||||
public class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
|
||||
protected String outDirSrc;
|
||||
|
||||
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
|
||||
protected List<String> files;
|
||||
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
|
||||
protected String outDirRes;
|
||||
|
||||
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
|
||||
protected String outDirName;
|
||||
|
||||
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
|
||||
protected int threadsCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||
protected boolean fallbackMode = false;
|
||||
|
||||
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
|
||||
@Parameter(names = { "-r", "--no-res" }, description = "do not decode resources")
|
||||
protected boolean skipResources = false;
|
||||
|
||||
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
|
||||
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
||||
protected boolean skipSources = false;
|
||||
|
||||
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
|
||||
@Parameter(names = { "--single-class" }, description = "decompile a single class")
|
||||
protected String singleClass = null;
|
||||
|
||||
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
||||
protected String outputFormat = "java";
|
||||
|
||||
@Parameter(names = { "-e", "--export-gradle" }, description = "save as android gradle project")
|
||||
protected boolean exportAsGradleProject = false;
|
||||
|
||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||
|
||||
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
|
||||
protected boolean showInconsistentCode = false;
|
||||
|
||||
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
|
||||
protected boolean cfgOutput = false;
|
||||
@Parameter(names = { "--no-imports" }, description = "disable use of imports, always write entire package name")
|
||||
protected boolean useImports = true;
|
||||
|
||||
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
|
||||
protected boolean rawCfgOutput = false;
|
||||
@Parameter(names = { "--no-debug-info" }, description = "disable debug info")
|
||||
protected boolean debugInfo = true;
|
||||
|
||||
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
|
||||
protected boolean verbose = false;
|
||||
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
|
||||
protected boolean inlineAnonymousClasses = true;
|
||||
|
||||
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
|
||||
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
||||
protected boolean replaceConsts = true;
|
||||
|
||||
@Parameter(names = { "--escape-unicode" }, description = "escape non latin characters in strings (with \\u)")
|
||||
protected boolean escapeUnicode = false;
|
||||
|
||||
@Parameter(names = { "--respect-bytecode-access-modifiers" }, description = "don't change original access modifiers")
|
||||
protected boolean respectBytecodeAccessModifiers = false;
|
||||
|
||||
@Parameter(names = { "--deobf" }, description = "activate deobfuscation")
|
||||
protected boolean deobfuscationOn = false;
|
||||
|
||||
@Parameter(names = {"--deobf-min"}, description = "min length of name")
|
||||
protected int deobfuscationMinLength = 2;
|
||||
@Parameter(names = { "--deobf-min" }, description = "min length of name, renamed if shorter")
|
||||
protected int deobfuscationMinLength = 3;
|
||||
|
||||
@Parameter(names = {"--deobf-max"}, description = "max length of name")
|
||||
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
|
||||
protected int deobfuscationMaxLength = 64;
|
||||
|
||||
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
|
||||
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to save deobfuscation map")
|
||||
protected boolean deobfuscationForceSave = false;
|
||||
|
||||
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
|
||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
|
||||
protected boolean deobfuscationParseKotlinMetadata = false;
|
||||
|
||||
@Parameter(
|
||||
names = { "--rename-flags" },
|
||||
description = "what to rename, comma-separated,"
|
||||
+ " 'case' for system case sensitivity,"
|
||||
+ " 'valid' for java identifiers,"
|
||||
+ " 'printable' characters,"
|
||||
+ " 'none' or 'all' (default)",
|
||||
converter = RenameConverter.class
|
||||
)
|
||||
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
||||
|
||||
@Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
|
||||
protected boolean fsCaseSensitive = false;
|
||||
|
||||
@Parameter(names = { "--cfg" }, description = "save methods control flow graph to dot file")
|
||||
protected boolean cfgOutput = false;
|
||||
|
||||
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
|
||||
protected boolean rawCfgOutput = false;
|
||||
|
||||
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||
protected boolean fallbackMode = false;
|
||||
|
||||
@Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)")
|
||||
protected boolean verbose = false;
|
||||
|
||||
@Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)")
|
||||
protected boolean quiet = false;
|
||||
|
||||
@Parameter(
|
||||
names = { "--log-level" },
|
||||
description = "set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG",
|
||||
converter = LogHelper.LogLevelConverter.class
|
||||
)
|
||||
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
|
||||
|
||||
@Parameter(names = { "--version" }, description = "print jadx version")
|
||||
protected boolean printVersion = false;
|
||||
|
||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
private final List<File> input = new ArrayList<File>(1);
|
||||
private File outputDir;
|
||||
|
||||
public boolean processArgs(String[] args) {
|
||||
return parse(args) && process();
|
||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
||||
return jcw.parse(args) && process(jcw);
|
||||
}
|
||||
|
||||
private boolean parse(String[] args) {
|
||||
try {
|
||||
new JCommander(this, args);
|
||||
return true;
|
||||
} catch (ParameterException e) {
|
||||
System.err.println("Arguments parse error: " + e.getMessage());
|
||||
printUsage();
|
||||
/**
|
||||
* Set values only for options provided in cmd.
|
||||
* Used to merge saved options and options passed in command line.
|
||||
*/
|
||||
public boolean overrideProvided(String[] args) {
|
||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(newInstance());
|
||||
if (!jcw.parse(args)) {
|
||||
return false;
|
||||
}
|
||||
jcw.overrideProvided(this);
|
||||
return process(jcw);
|
||||
}
|
||||
|
||||
private boolean process() {
|
||||
if (isPrintHelp()) {
|
||||
printUsage();
|
||||
protected JadxCLIArgs newInstance() {
|
||||
return new JadxCLIArgs();
|
||||
}
|
||||
|
||||
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
|
||||
if (printHelp) {
|
||||
jcw.printUsage();
|
||||
return false;
|
||||
}
|
||||
if (printVersion) {
|
||||
System.out.println(JadxDecompiler.getVersion());
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (threadsCount <= 0) {
|
||||
throw new JadxException("Threads count must be positive");
|
||||
}
|
||||
if (files != null) {
|
||||
for (String fileName : files) {
|
||||
File file = new File(fileName);
|
||||
if (file.exists()) {
|
||||
input.add(file);
|
||||
} else {
|
||||
throw new JadxException("File not found: " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input.size() > 1) {
|
||||
throw new JadxException("Only one input file is supported");
|
||||
}
|
||||
if (outDirName != null) {
|
||||
outputDir = new File(outDirName);
|
||||
}
|
||||
if (isVerbose()) {
|
||||
ch.qos.logback.classic.Logger rootLogger =
|
||||
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
rootLogger.setLevel(ch.qos.logback.classic.Level.DEBUG);
|
||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
||||
}
|
||||
LogHelper.setLogLevelFromArgs(this);
|
||||
} catch (JadxException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
printUsage();
|
||||
jcw.printUsage();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
JCommander jc = new JCommander(this);
|
||||
// print usage in not sorted fields order (by default its sorted by description)
|
||||
PrintStream out = System.out;
|
||||
out.println();
|
||||
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
||||
out.println();
|
||||
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
|
||||
out.println("options:");
|
||||
|
||||
List<ParameterDescription> params = jc.getParameters();
|
||||
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<String, ParameterDescription>(params.size());
|
||||
int maxNamesLen = 0;
|
||||
for (ParameterDescription p : params) {
|
||||
paramsMap.put(p.getParameterized().getName(), p);
|
||||
int len = p.getNames().length();
|
||||
if (len > maxNamesLen) {
|
||||
maxNamesLen = len;
|
||||
}
|
||||
public JadxArgs toJadxArgs() {
|
||||
JadxArgs args = new JadxArgs();
|
||||
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
|
||||
args.setOutDir(FileUtils.toFile(outDir));
|
||||
args.setOutDirSrc(FileUtils.toFile(outDirSrc));
|
||||
args.setOutDirRes(FileUtils.toFile(outDirRes));
|
||||
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
||||
args.setThreadsCount(threadsCount);
|
||||
args.setSkipSources(skipSources);
|
||||
if (singleClass != null) {
|
||||
args.setClassFilter(className -> singleClass.equals(className));
|
||||
}
|
||||
Field[] fields = JadxCLIArgs.class.getDeclaredFields();
|
||||
for (Field f : fields) {
|
||||
String name = f.getName();
|
||||
ParameterDescription p = paramsMap.get(name);
|
||||
if (p == null) {
|
||||
continue;
|
||||
}
|
||||
StringBuilder opt = new StringBuilder();
|
||||
opt.append(' ').append(p.getNames());
|
||||
addSpaces(opt, maxNamesLen - opt.length() + 2);
|
||||
opt.append("- ").append(p.getDescription());
|
||||
out.println(opt);
|
||||
}
|
||||
out.println("Example:");
|
||||
out.println(" jadx -d out classes.dex");
|
||||
args.setSkipResources(skipResources);
|
||||
args.setFallbackMode(fallbackMode);
|
||||
args.setShowInconsistentCode(showInconsistentCode);
|
||||
args.setCfgOutput(cfgOutput);
|
||||
args.setRawCFGOutput(rawCfgOutput);
|
||||
args.setReplaceConsts(replaceConsts);
|
||||
args.setDeobfuscationOn(deobfuscationOn);
|
||||
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||
args.setExportAsGradleProject(exportAsGradleProject);
|
||||
args.setUseImports(useImports);
|
||||
args.setDebugInfo(debugInfo);
|
||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||
args.setRenameCaseSensitive(isRenameCaseSensitive());
|
||||
args.setRenameValid(isRenameValid());
|
||||
args.setRenamePrintable(isRenamePrintable());
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
return args;
|
||||
}
|
||||
|
||||
private static void addSpaces(StringBuilder str, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
str.append(' ');
|
||||
}
|
||||
public List<String> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public List<File> getInput() {
|
||||
return input;
|
||||
public String getOutDir() {
|
||||
return outDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getOutDir() {
|
||||
return outputDir;
|
||||
public String getOutDirSrc() {
|
||||
return outDirSrc;
|
||||
}
|
||||
|
||||
public void setOutputDir(File outputDir) {
|
||||
this.outputDir = outputDir;
|
||||
public String getOutDirRes() {
|
||||
return outDirRes;
|
||||
}
|
||||
|
||||
public boolean isPrintHelp() {
|
||||
return printHelp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipResources() {
|
||||
return skipResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return skipSources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThreadsCount() {
|
||||
return threadsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCFGOutput() {
|
||||
return cfgOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRawCFGOutput() {
|
||||
return rawCfgOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFallbackMode() {
|
||||
return fallbackMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return verbose;
|
||||
public boolean isUseImports() {
|
||||
return useImports;
|
||||
}
|
||||
|
||||
public boolean isDebugInfo() {
|
||||
return debugInfo;
|
||||
}
|
||||
|
||||
public boolean isInlineAnonymousClasses() {
|
||||
return inlineAnonymousClasses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationParseKotlinMetadata() {
|
||||
return deobfuscationParseKotlinMetadata;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public boolean isCfgOutput() {
|
||||
return cfgOutput;
|
||||
}
|
||||
|
||||
public boolean isRawCfgOutput() {
|
||||
return rawCfgOutput;
|
||||
}
|
||||
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
public boolean isRespectBytecodeAccessModifiers() {
|
||||
return respectBytecodeAccessModifiers;
|
||||
}
|
||||
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
|
||||
public boolean isRenameCaseSensitive() {
|
||||
return renameFlags.contains(RenameEnum.CASE);
|
||||
}
|
||||
|
||||
public boolean isRenameValid() {
|
||||
return renameFlags.contains(RenameEnum.VALID);
|
||||
}
|
||||
|
||||
public boolean isRenamePrintable() {
|
||||
return renameFlags.contains(RenameEnum.PRINTABLE);
|
||||
}
|
||||
|
||||
public boolean isFsCaseSensitive() {
|
||||
return fsCaseSensitive;
|
||||
}
|
||||
|
||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||
private final String paramName;
|
||||
|
||||
RenameConverter(String paramName) {
|
||||
this.paramName = paramName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RenameEnum> convert(String value) {
|
||||
if (value.equalsIgnoreCase("NONE")) {
|
||||
return EnumSet.noneOf(RenameEnum.class);
|
||||
}
|
||||
if (value.equalsIgnoreCase("ALL")) {
|
||||
return EnumSet.allOf(RenameEnum.class);
|
||||
}
|
||||
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
|
||||
for (String s : value.split(",")) {
|
||||
try {
|
||||
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(
|
||||
'\'' + s + "' is unknown for parameter " + paramName
|
||||
+ ", possible values are " + enumValuesString(RenameEnum.values()));
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
public static String enumValuesString(Enum<?>[] values) {
|
||||
return Stream.of(values)
|
||||
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package jadx.cli;
|
||||
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
|
||||
public class LogHelper {
|
||||
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogHelper.class);
|
||||
|
||||
public enum LogLevelEnum {
|
||||
QUIET(Level.OFF),
|
||||
PROGRESS(Level.OFF),
|
||||
ERROR(Level.ERROR),
|
||||
WARN(Level.WARN),
|
||||
INFO(Level.INFO),
|
||||
DEBUG(Level.DEBUG);
|
||||
|
||||
private final Level level;
|
||||
|
||||
LogLevelEnum(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLogLevelFromArgs(JadxCLIArgs args) {
|
||||
if (isCustomLogConfig()) {
|
||||
return;
|
||||
}
|
||||
LogLevelEnum logLevel = args.logLevel;
|
||||
if (args.quiet) {
|
||||
logLevel = LogLevelEnum.QUIET;
|
||||
} else if (args.verbose) {
|
||||
logLevel = LogLevelEnum.DEBUG;
|
||||
}
|
||||
|
||||
applyLogLevel(logLevel);
|
||||
}
|
||||
|
||||
public static void applyLogLevel(LogLevelEnum logLevel) {
|
||||
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
rootLogger.setLevel(logLevel.getLevel());
|
||||
|
||||
if (logLevel != LogLevelEnum.QUIET) {
|
||||
// show progress for all levels except quiet
|
||||
setLevelForClass(JadxCLI.class, Level.INFO);
|
||||
setLevelForClass(JadxDecompiler.class, Level.INFO);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setLevelForClass(Class<?> cls, Level level) {
|
||||
((Logger) LoggerFactory.getLogger(cls)).setLevel(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to detect if user provide custom logback config via -Dlogback.configurationFile=
|
||||
*/
|
||||
private static boolean isCustomLogConfig() {
|
||||
try {
|
||||
String logbackConfig = System.getProperty("logback.configurationFile");
|
||||
if (logbackConfig == null) {
|
||||
return false;
|
||||
}
|
||||
LOG.debug("Use custom log config: {}", logbackConfig);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to detect custom log config", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class LogLevelConverter implements IStringConverter<LogLevelEnum> {
|
||||
|
||||
@Override
|
||||
public LogLevelEnum convert(String value) {
|
||||
try {
|
||||
return LogLevelEnum.valueOf(value.toUpperCase());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(
|
||||
'\'' + value + "' is unknown log level, possible values are "
|
||||
+ JadxCLIArgs.enumValuesString(LogLevelEnum.values()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package jadx.cli.clst;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.core.clsp.ClsSet;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
/**
|
||||
* Utility class for convert dex or jar to jadx classes set (.jcst)
|
||||
*/
|
||||
public class ConvertToClsSet {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
||||
|
||||
public static void usage() {
|
||||
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length < 2) {
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
|
||||
Path output = inputPaths.remove(0);
|
||||
|
||||
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||
List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
|
||||
}
|
||||
|
||||
JadxArgs jadxArgs = new JadxArgs();
|
||||
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
|
||||
RootNode root = new RootNode(jadxArgs);
|
||||
root.loadClasses(loadedInputs);
|
||||
|
||||
ClsSet set = new ClsSet(root);
|
||||
set.loadFrom(root);
|
||||
set.save(output);
|
||||
|
||||
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
|
||||
LOG.info("done");
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package jadx.cli;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class JadxCLIArgsTest {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLIArgsTest.class);
|
||||
|
||||
@Test
|
||||
public void testInvertedBooleanOption() {
|
||||
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
|
||||
assertThat(parse("").isReplaceConsts(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeUnicodeOption() {
|
||||
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
|
||||
assertThat(parse("").isEscapeUnicode(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSrcOption() {
|
||||
assertThat(parse("--no-src").isSkipSources(), is(true));
|
||||
assertThat(parse("-s").isSkipSources(), is(true));
|
||||
assertThat(parse("").isSkipSources(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionsOverride() {
|
||||
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
|
||||
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
|
||||
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
|
||||
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "--no-imports").isUseImports(), is(false));
|
||||
args.debugInfo = false;
|
||||
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
|
||||
|
||||
args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "").isUseImports(), is(false));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
return parse(new JadxCLIArgs(), args);
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
|
||||
boolean res = jadxArgs.processArgs(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
|
||||
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
|
||||
boolean res = jadxArgs.overrideProvided(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.cli.JadxCLIArgs.RenameConverter;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class RenameConverterTest {
|
||||
|
||||
private RenameConverter converter;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
converter = new RenameConverter("someParam");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void all() {
|
||||
Set<RenameEnum> set = converter.convert("all");
|
||||
assertEquals(3, set.size());
|
||||
assertTrue(set.contains(RenameEnum.CASE));
|
||||
assertTrue(set.contains(RenameEnum.VALID));
|
||||
assertTrue(set.contains(RenameEnum.PRINTABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void none() {
|
||||
Set<RenameEnum> set = converter.convert("none");
|
||||
assertTrue(set.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrong() {
|
||||
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
|
||||
() -> converter.convert("wrong"),
|
||||
"Expected convert() to throw, but it didn't");
|
||||
|
||||
assertEquals("'wrong' is unknown for parameter someParam, "
|
||||
+ "possible values are 'case', 'valid', 'printable'",
|
||||
thrown.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestInput {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TestInput.class);
|
||||
|
||||
@Test
|
||||
public void testDexInput() throws Exception {
|
||||
decompile("dex", "samples/hello.dex");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmaliInput() throws Exception {
|
||||
decompile("smali", "samples/HelloWorld.smali");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassInput() throws Exception {
|
||||
decompile("class", "samples/HelloWorld.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleInput() throws Exception {
|
||||
decompile("multi", "samples/hello.dex", "samples/HelloWorld.smali");
|
||||
}
|
||||
|
||||
private void decompile(String tmpDirName, String... inputSamples) throws URISyntaxException, IOException {
|
||||
StringBuilder args = new StringBuilder();
|
||||
Path tempDir = FileUtils.createTempDir(tmpDirName);
|
||||
args.append("-v");
|
||||
args.append(" -d ").append(tempDir.toAbsolutePath());
|
||||
|
||||
for (String inputSample : inputSamples) {
|
||||
URL resource = getClass().getClassLoader().getResource(inputSample);
|
||||
assertThat(resource).isNotNull();
|
||||
String sampleFile = resource.toURI().getRawPath();
|
||||
args.append(' ').append(sampleFile);
|
||||
}
|
||||
|
||||
int result = JadxCLI.execute(args.toString().split(" "));
|
||||
assertThat(result).isEqualTo(0);
|
||||
List<Path> resultJavaFiles = collectJavaFilesInDir(tempDir);
|
||||
assertThat(resultJavaFiles).isNotEmpty();
|
||||
|
||||
// do not copy input files as resources
|
||||
PathMatcher logAllFiles = path -> {
|
||||
LOG.debug("File in result dir: {}", path);
|
||||
return true;
|
||||
};
|
||||
for (Path path : collectFilesInDir(tempDir, logAllFiles)) {
|
||||
for (String inputSample : inputSamples) {
|
||||
assertThat(path.toAbsolutePath().toString()).doesNotContain(inputSample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Path> collectJavaFilesInDir(Path dir) throws IOException {
|
||||
PathMatcher javaMatcher = dir.getFileSystem().getPathMatcher("glob:**.java");
|
||||
return collectFilesInDir(dir, javaMatcher);
|
||||
}
|
||||
|
||||
private static List<Path> collectFilesInDir(Path dir, PathMatcher matcher) throws IOException {
|
||||
try (Stream<Path> pathStream = Files.walk(dir)) {
|
||||
return pathStream
|
||||
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
|
||||
.filter(matcher::matches)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
FileUtils.clearTempRootDir();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
.class Lsmali/HelloWorld;
|
||||
.super Ljava/lang/Object;
|
||||
.source "HelloWorld.java"
|
||||
|
||||
.method constructor <init>()V
|
||||
.registers 1
|
||||
|
||||
.line 1
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method public static main([Ljava/lang/String;)V
|
||||
.registers 2
|
||||
|
||||
.line 3
|
||||
sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
|
||||
const-string v0, "Hello, World"
|
||||
|
||||
invoke-virtual {p0, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
|
||||
.line 4
|
||||
return-void
|
||||
.end method
|
||||
Binary file not shown.
+15
-20
@@ -1,27 +1,22 @@
|
||||
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
|
||||
|
||||
apply plugin: "info.solidsoft.pitest"
|
||||
plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
runtime files(jadxClasspath)
|
||||
runtimeOnly files('clsp-data/android-29-clst.jar')
|
||||
runtimeOnly files('clsp-data/android-29-res.jar')
|
||||
|
||||
compile files('lib/dx-1.10.jar')
|
||||
compile 'commons-io:commons-io:2.4'
|
||||
compile 'org.ow2.asm:asm:5.0.3'
|
||||
compile 'com.intellij:annotations:12.0'
|
||||
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||
|
||||
testCompile 'org.smali:smali:2.0.3'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
|
||||
testImplementation 'org.apache.commons:commons-lang3:3.11'
|
||||
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-dex-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-smali-input'))
|
||||
testRuntimeOnly(project(':jadx-plugins:jadx-java-convert'))
|
||||
}
|
||||
|
||||
task packTests(type: Jar) {
|
||||
classifier = 'tests'
|
||||
from sourceSets.test.output
|
||||
}
|
||||
|
||||
pitest {
|
||||
excludedMethods = ['toString']
|
||||
threads = 4
|
||||
enableDefaultIncrementalAnalysis = true
|
||||
outputFormats = ['XML', 'HTML']
|
||||
jvmArgs = ['-Xmx12G']
|
||||
test {
|
||||
exclude '**/tmp/*'
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,24 +2,32 @@ package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final JavaClass cls;
|
||||
private final JavaNode node;
|
||||
private final int line;
|
||||
private final int offset;
|
||||
|
||||
public CodePosition(JavaClass cls, int line, int offset) {
|
||||
this.cls = cls;
|
||||
public CodePosition(JavaNode node, int line, int offset) {
|
||||
this.node = node;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public CodePosition(int line, int offset) {
|
||||
this.cls = null;
|
||||
this.node = null;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public JavaNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public JavaClass getJavaClass() {
|
||||
return cls;
|
||||
JavaClass parent = node.getDeclaringClass();
|
||||
if (parent == null && node instanceof JavaClass) {
|
||||
return (JavaClass) node;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
@@ -30,10 +38,6 @@ public final class CodePosition {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return line != 0 || offset != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -53,6 +57,14 @@ public final class CodePosition {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return line + ":" + offset + (cls != null ? " " + cls : "");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(line);
|
||||
if (offset != 0) {
|
||||
sb.append(':').append(offset);
|
||||
}
|
||||
if (node != null) {
|
||||
sb.append(' ').append(node);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class DefaultJadxArgs implements IJadxArgs {
|
||||
|
||||
@Override
|
||||
public File getOutDir() {
|
||||
return new File("jadx-output");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThreadsCount() {
|
||||
return Runtime.getRuntime().availableProcessors();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCFGOutput() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRawCFGOutput() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFallbackMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowInconsistentCode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipResources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationOn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return Integer.MIN_VALUE + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return Integer.MAX_VALUE - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package jadx.api;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ICodeCache {
|
||||
|
||||
void add(String clsFullName, ICodeInfo codeInfo);
|
||||
|
||||
void remove(String clsFullName);
|
||||
|
||||
@Nullable
|
||||
ICodeInfo get(String clsFullName);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
|
||||
public interface ICodeInfo {
|
||||
|
||||
ICodeInfo EMPTY = new SimpleCodeInfo("");
|
||||
|
||||
String getCodeStr();
|
||||
|
||||
Map<Integer, Integer> getLineMapping();
|
||||
|
||||
Map<CodePosition, Object> getAnnotations();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface IJadxArgs {
|
||||
File getOutDir();
|
||||
|
||||
int getThreadsCount();
|
||||
|
||||
boolean isCFGOutput();
|
||||
|
||||
boolean isRawCFGOutput();
|
||||
|
||||
boolean isFallbackMode();
|
||||
|
||||
boolean isShowInconsistentCode();
|
||||
|
||||
boolean isVerbose();
|
||||
|
||||
boolean isSkipResources();
|
||||
|
||||
boolean isSkipSources();
|
||||
|
||||
boolean isDeobfuscationOn();
|
||||
|
||||
int getDeobfuscationMinLength();
|
||||
|
||||
int getDeobfuscationMaxLength();
|
||||
|
||||
boolean isDeobfuscationForceSave();
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
|
||||
public class JadxArgs {
|
||||
|
||||
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||
|
||||
public static final String DEFAULT_OUT_DIR = "jadx-output";
|
||||
public static final String DEFAULT_SRC_DIR = "sources";
|
||||
public static final String DEFAULT_RES_DIR = "resources";
|
||||
|
||||
private List<File> inputFiles = new ArrayList<>(1);
|
||||
|
||||
private File outDir;
|
||||
private File outDirSrc;
|
||||
private File outDirRes;
|
||||
|
||||
private ICodeCache codeCache = new InMemoryCodeCache();
|
||||
|
||||
private int threadsCount = DEFAULT_THREADS_COUNT;
|
||||
|
||||
private boolean cfgOutput = false;
|
||||
private boolean rawCFGOutput = false;
|
||||
|
||||
private boolean fallbackMode = false;
|
||||
private boolean showInconsistentCode = false;
|
||||
|
||||
private boolean useImports = true;
|
||||
private boolean debugInfo = true;
|
||||
private boolean inlineAnonymousClasses = true;
|
||||
|
||||
private boolean skipResources = false;
|
||||
private boolean skipSources = false;
|
||||
|
||||
/**
|
||||
* Predicate that allows to filter the classes to be process based on their full name
|
||||
*/
|
||||
private Predicate<String> classFilter = null;
|
||||
|
||||
private boolean deobfuscationOn = false;
|
||||
private boolean deobfuscationForceSave = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
private boolean parseKotlinMetadata = false;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
|
||||
private boolean escapeUnicode = false;
|
||||
private boolean replaceConsts = true;
|
||||
private boolean respectBytecodeAccModifiers = false;
|
||||
private boolean exportAsGradleProject = false;
|
||||
|
||||
private boolean fsCaseSensitive;
|
||||
|
||||
public enum RenameEnum {
|
||||
CASE, VALID, PRINTABLE
|
||||
}
|
||||
|
||||
private Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
||||
|
||||
public enum OutputFormatEnum {
|
||||
JAVA, JSON
|
||||
}
|
||||
|
||||
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
||||
|
||||
public JadxArgs() {
|
||||
// use default options
|
||||
}
|
||||
|
||||
public void setRootDir(File rootDir) {
|
||||
setOutDir(rootDir);
|
||||
setOutDirSrc(new File(rootDir, DEFAULT_SRC_DIR));
|
||||
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
|
||||
}
|
||||
|
||||
public List<File> getInputFiles() {
|
||||
return inputFiles;
|
||||
}
|
||||
|
||||
public void setInputFile(File inputFile) {
|
||||
this.inputFiles = Collections.singletonList(inputFile);
|
||||
}
|
||||
|
||||
public void setInputFiles(List<File> inputFiles) {
|
||||
this.inputFiles = inputFiles;
|
||||
}
|
||||
|
||||
public File getOutDir() {
|
||||
return outDir;
|
||||
}
|
||||
|
||||
public void setOutDir(File outDir) {
|
||||
this.outDir = outDir;
|
||||
}
|
||||
|
||||
public File getOutDirSrc() {
|
||||
return outDirSrc;
|
||||
}
|
||||
|
||||
public void setOutDirSrc(File outDirSrc) {
|
||||
this.outDirSrc = outDirSrc;
|
||||
}
|
||||
|
||||
public File getOutDirRes() {
|
||||
return outDirRes;
|
||||
}
|
||||
|
||||
public void setOutDirRes(File outDirRes) {
|
||||
this.outDirRes = outDirRes;
|
||||
}
|
||||
|
||||
public int getThreadsCount() {
|
||||
return threadsCount;
|
||||
}
|
||||
|
||||
public void setThreadsCount(int threadsCount) {
|
||||
this.threadsCount = threadsCount;
|
||||
}
|
||||
|
||||
public boolean isCfgOutput() {
|
||||
return cfgOutput;
|
||||
}
|
||||
|
||||
public void setCfgOutput(boolean cfgOutput) {
|
||||
this.cfgOutput = cfgOutput;
|
||||
}
|
||||
|
||||
public boolean isRawCFGOutput() {
|
||||
return rawCFGOutput;
|
||||
}
|
||||
|
||||
public void setRawCFGOutput(boolean rawCFGOutput) {
|
||||
this.rawCFGOutput = rawCFGOutput;
|
||||
}
|
||||
|
||||
public boolean isFallbackMode() {
|
||||
return fallbackMode;
|
||||
}
|
||||
|
||||
public void setFallbackMode(boolean fallbackMode) {
|
||||
this.fallbackMode = fallbackMode;
|
||||
}
|
||||
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
|
||||
public void setShowInconsistentCode(boolean showInconsistentCode) {
|
||||
this.showInconsistentCode = showInconsistentCode;
|
||||
}
|
||||
|
||||
public boolean isUseImports() {
|
||||
return useImports;
|
||||
}
|
||||
|
||||
public void setUseImports(boolean useImports) {
|
||||
this.useImports = useImports;
|
||||
}
|
||||
|
||||
public boolean isDebugInfo() {
|
||||
return debugInfo;
|
||||
}
|
||||
|
||||
public void setDebugInfo(boolean debugInfo) {
|
||||
this.debugInfo = debugInfo;
|
||||
}
|
||||
|
||||
public boolean isInlineAnonymousClasses() {
|
||||
return inlineAnonymousClasses;
|
||||
}
|
||||
|
||||
public void setInlineAnonymousClasses(boolean inlineAnonymousClasses) {
|
||||
this.inlineAnonymousClasses = inlineAnonymousClasses;
|
||||
}
|
||||
|
||||
public boolean isSkipResources() {
|
||||
return skipResources;
|
||||
}
|
||||
|
||||
public void setSkipResources(boolean skipResources) {
|
||||
this.skipResources = skipResources;
|
||||
}
|
||||
|
||||
public boolean isSkipSources() {
|
||||
return skipSources;
|
||||
}
|
||||
|
||||
public void setSkipSources(boolean skipSources) {
|
||||
this.skipSources = skipSources;
|
||||
}
|
||||
|
||||
public Predicate<String> getClassFilter() {
|
||||
return classFilter;
|
||||
}
|
||||
|
||||
public void setClassFilter(Predicate<String> classFilter) {
|
||||
this.classFilter = classFilter;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
|
||||
public void setDeobfuscationOn(boolean deobfuscationOn) {
|
||||
this.deobfuscationOn = deobfuscationOn;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
||||
this.deobfuscationForceSave = deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public boolean isUseSourceNameAsClassAlias() {
|
||||
return useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
|
||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public boolean isParseKotlinMetadata() {
|
||||
return parseKotlinMetadata;
|
||||
}
|
||||
|
||||
public void setParseKotlinMetadata(boolean parseKotlinMetadata) {
|
||||
this.parseKotlinMetadata = parseKotlinMetadata;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
|
||||
this.deobfuscationMinLength = deobfuscationMinLength;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
|
||||
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public void setEscapeUnicode(boolean escapeUnicode) {
|
||||
this.escapeUnicode = escapeUnicode;
|
||||
}
|
||||
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
public void setReplaceConsts(boolean replaceConsts) {
|
||||
this.replaceConsts = replaceConsts;
|
||||
}
|
||||
|
||||
public boolean isRespectBytecodeAccModifiers() {
|
||||
return respectBytecodeAccModifiers;
|
||||
}
|
||||
|
||||
public void setRespectBytecodeAccModifiers(boolean respectBytecodeAccModifiers) {
|
||||
this.respectBytecodeAccModifiers = respectBytecodeAccModifiers;
|
||||
}
|
||||
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
|
||||
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||
this.exportAsGradleProject = exportAsGradleProject;
|
||||
}
|
||||
|
||||
public boolean isFsCaseSensitive() {
|
||||
return fsCaseSensitive;
|
||||
}
|
||||
|
||||
public void setFsCaseSensitive(boolean fsCaseSensitive) {
|
||||
this.fsCaseSensitive = fsCaseSensitive;
|
||||
}
|
||||
|
||||
public boolean isRenameCaseSensitive() {
|
||||
return renameFlags.contains(RenameEnum.CASE);
|
||||
}
|
||||
|
||||
public void setRenameCaseSensitive(boolean renameCaseSensitive) {
|
||||
updateRenameFlag(renameCaseSensitive, RenameEnum.CASE);
|
||||
}
|
||||
|
||||
public boolean isRenameValid() {
|
||||
return renameFlags.contains(RenameEnum.VALID);
|
||||
}
|
||||
|
||||
public void setRenameValid(boolean renameValid) {
|
||||
updateRenameFlag(renameValid, RenameEnum.VALID);
|
||||
}
|
||||
|
||||
public boolean isRenamePrintable() {
|
||||
return renameFlags.contains(RenameEnum.PRINTABLE);
|
||||
}
|
||||
|
||||
public void setRenamePrintable(boolean renamePrintable) {
|
||||
updateRenameFlag(renamePrintable, RenameEnum.PRINTABLE);
|
||||
}
|
||||
|
||||
private void updateRenameFlag(boolean enabled, RenameEnum flag) {
|
||||
if (enabled) {
|
||||
renameFlags.add(flag);
|
||||
} else {
|
||||
renameFlags.remove(flag);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRenameFlags(Set<RenameEnum> renameFlags) {
|
||||
this.renameFlags = renameFlags;
|
||||
}
|
||||
|
||||
public Set<RenameEnum> getRenameFlags() {
|
||||
return renameFlags;
|
||||
}
|
||||
|
||||
public OutputFormatEnum getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
public boolean isJsonOutput() {
|
||||
return outputFormat == OutputFormatEnum.JSON;
|
||||
}
|
||||
|
||||
public void setOutputFormat(OutputFormatEnum outputFormat) {
|
||||
this.outputFormat = outputFormat;
|
||||
}
|
||||
|
||||
public ICodeCache getCodeCache() {
|
||||
return codeCache;
|
||||
}
|
||||
|
||||
public void setCodeCache(ICodeCache codeCache) {
|
||||
this.codeCache = codeCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||
+ ", outDir=" + outDir
|
||||
+ ", outDirSrc=" + outDirSrc
|
||||
+ ", outDirRes=" + outDirRes
|
||||
+ ", threadsCount=" + threadsCount
|
||||
+ ", cfgOutput=" + cfgOutput
|
||||
+ ", rawCFGOutput=" + rawCFGOutput
|
||||
+ ", fallbackMode=" + fallbackMode
|
||||
+ ", showInconsistentCode=" + showInconsistentCode
|
||||
+ ", useImports=" + useImports
|
||||
+ ", skipResources=" + skipResources
|
||||
+ ", skipSources=" + skipSources
|
||||
+ ", deobfuscationOn=" + deobfuscationOn
|
||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", parseKotlinMetadata=" + parseKotlinMetadata
|
||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||
+ ", escapeUnicode=" + escapeUnicode
|
||||
+ ", replaceConsts=" + replaceConsts
|
||||
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
|
||||
+ ", exportAsGradleProject=" + exportAsGradleProject
|
||||
+ ", fsCaseSensitive=" + fsCaseSensitive
|
||||
+ ", renameFlags=" + renameFlags
|
||||
+ ", outputFormat=" + outputFormat
|
||||
+ ", codeCache=" + codeCache
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
|
||||
public class JadxArgsValidator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
|
||||
|
||||
public static void validate(JadxArgs args) {
|
||||
checkInputFiles(args);
|
||||
validateOutDirs(args);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Effective jadx args: {}", args);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkInputFiles(JadxArgs args) {
|
||||
List<File> inputFiles = args.getInputFiles();
|
||||
if (inputFiles.isEmpty()) {
|
||||
throw new JadxArgsValidateException("Please specify input file");
|
||||
}
|
||||
for (File inputFile : inputFiles) {
|
||||
String fileName = inputFile.getName();
|
||||
if (fileName.startsWith("--")) {
|
||||
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
||||
}
|
||||
}
|
||||
for (File file : inputFiles) {
|
||||
checkFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateOutDirs(JadxArgs args) {
|
||||
File outDir = args.getOutDir();
|
||||
File srcDir = args.getOutDirSrc();
|
||||
File resDir = args.getOutDirRes();
|
||||
if (outDir == null) {
|
||||
if (srcDir != null) {
|
||||
outDir = srcDir;
|
||||
} else if (resDir != null) {
|
||||
outDir = resDir;
|
||||
} else {
|
||||
outDir = makeDirFromInput(args);
|
||||
}
|
||||
args.setOutDir(outDir);
|
||||
}
|
||||
if (srcDir == null) {
|
||||
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
|
||||
}
|
||||
if (resDir == null) {
|
||||
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
|
||||
}
|
||||
|
||||
checkDir(args.getOutDir(), "Output");
|
||||
checkDir(args.getOutDirSrc(), "Source output");
|
||||
checkDir(args.getOutDirRes(), "Resources output");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static File makeDirFromInput(JadxArgs args) {
|
||||
File outDir;
|
||||
String outDirName;
|
||||
File file = args.getInputFiles().get(0);
|
||||
String name = file.getName();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
outDirName = name.substring(0, pos);
|
||||
} else {
|
||||
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
|
||||
}
|
||||
LOG.info("output directory: {}", outDirName);
|
||||
outDir = new File(outDirName);
|
||||
return outDir;
|
||||
}
|
||||
|
||||
private static void checkFile(File file) {
|
||||
if (!file.exists()) {
|
||||
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
throw new JadxArgsValidateException("Expected file but found directory instead: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDir(File dir, String desc) {
|
||||
if (dir != null && dir.exists() && !dir.isDirectory()) {
|
||||
throw new JadxArgsValidateException(desc + " directory exists as file " + dir);
|
||||
}
|
||||
}
|
||||
|
||||
private JadxArgsValidator() {
|
||||
}
|
||||
}
|
||||
@@ -1,123 +1,158 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
import jadx.core.xmlgen.ResourcesSaver;
|
||||
|
||||
/**
|
||||
* Jadx API usage example:
|
||||
* <pre><code>
|
||||
* JadxDecompiler jadx = new JadxDecompiler();
|
||||
* jadx.loadFile(new File("classes.dex"));
|
||||
* jadx.setOutputDir(new File("out"));
|
||||
* jadx.save();
|
||||
* </code></pre>
|
||||
* <p/>
|
||||
* Instead of 'save()' you can get list of decompiled classes:
|
||||
* <pre><code>
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* JadxArgs args = new JadxArgs();
|
||||
* args.getInputFiles().add(new File("test.apk"));
|
||||
* args.setOutDir(new File("jadx-test-output"));
|
||||
* try (JadxDecompiler jadx = new JadxDecompiler(args)) {
|
||||
* jadx.load();
|
||||
* jadx.save();
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
* <p>
|
||||
* Instead of 'save()' you can iterate over decompiled classes:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* for(JavaClass cls : jadx.getClasses()) {
|
||||
* System.out.println(cls.getCode());
|
||||
* }
|
||||
* </code></pre>
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
public final class JadxDecompiler {
|
||||
public final class JadxDecompiler implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||
|
||||
private final IJadxArgs args;
|
||||
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
|
||||
|
||||
private File outDir;
|
||||
private final JadxArgs args;
|
||||
private final JadxPluginManager pluginManager = new JadxPluginManager();
|
||||
private final List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||
|
||||
private RootNode root;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
private CodeGen codeGen;
|
||||
|
||||
private List<JavaClass> classes;
|
||||
private List<ResourceFile> resources;
|
||||
|
||||
private BinaryXMLParser xmlParser;
|
||||
|
||||
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new DefaultJadxArgs());
|
||||
this(new JadxArgs());
|
||||
}
|
||||
|
||||
public JadxDecompiler(IJadxArgs jadxArgs) {
|
||||
this.args = jadxArgs;
|
||||
this.outDir = jadxArgs.getOutDir();
|
||||
public JadxDecompiler(JadxArgs args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
reset();
|
||||
init();
|
||||
JadxArgsValidator.validate(args);
|
||||
LOG.info("loading ...");
|
||||
loadInputFiles();
|
||||
|
||||
root = new RootNode(args);
|
||||
root.loadClasses(loadedInputs);
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.runPreDecompileStage();
|
||||
root.initPasses();
|
||||
}
|
||||
|
||||
public void setOutputDir(File outDir) {
|
||||
this.outDir = outDir;
|
||||
init();
|
||||
}
|
||||
|
||||
void init() {
|
||||
if (outDir == null) {
|
||||
outDir = new DefaultJadxArgs().getOutDir();
|
||||
private void loadInputFiles() {
|
||||
loadedInputs.clear();
|
||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||
ILoadResult loadResult = inputPlugin.loadFiles(inputPaths);
|
||||
if (loadResult != null && !loadResult.isEmpty()) {
|
||||
loadedInputs.add(loadResult);
|
||||
}
|
||||
}
|
||||
this.passes = Jadx.getPassesList(args, outDir);
|
||||
this.codeGen = new CodeGen(args);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
private void reset() {
|
||||
root = null;
|
||||
classes = null;
|
||||
resources = null;
|
||||
xmlParser = null;
|
||||
root = null;
|
||||
passes = null;
|
||||
codeGen = null;
|
||||
|
||||
classesMap.clear();
|
||||
methodsMap.clear();
|
||||
fieldsMap.clear();
|
||||
|
||||
closeInputs();
|
||||
}
|
||||
|
||||
private void closeInputs() {
|
||||
loadedInputs.forEach(load -> {
|
||||
try {
|
||||
load.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to close input", e);
|
||||
}
|
||||
});
|
||||
loadedInputs.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public void registerPlugin(JadxPlugin plugin) {
|
||||
pluginManager.register(plugin);
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
return Jadx.getVersion();
|
||||
}
|
||||
|
||||
public void loadFile(File file) throws JadxException {
|
||||
loadFiles(Collections.singletonList(file));
|
||||
}
|
||||
|
||||
public void loadFiles(List<File> files) throws JadxException {
|
||||
if (files.isEmpty()) {
|
||||
throw new JadxException("Empty file list");
|
||||
}
|
||||
inputFiles.clear();
|
||||
for (File file : files) {
|
||||
try {
|
||||
inputFiles.add(new InputFile(file));
|
||||
} catch (IOException e) {
|
||||
throw new JadxException("Error load file: " + file, e);
|
||||
}
|
||||
}
|
||||
parse();
|
||||
}
|
||||
|
||||
public void save() {
|
||||
save(!args.isSkipSources(), !args.isSkipResources());
|
||||
}
|
||||
@@ -131,12 +166,13 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
private void save(boolean saveSources, boolean saveResources) {
|
||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||
ex.shutdown();
|
||||
try {
|
||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||
ex.shutdown();
|
||||
ex.awaitTermination(1, TimeUnit.DAYS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new JadxRuntimeException("Save interrupted", e);
|
||||
LOG.error("Save interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,44 +189,73 @@ public final class JadxDecompiler {
|
||||
|
||||
LOG.info("processing ...");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||
if (saveSources) {
|
||||
for (final JavaClass cls : getClasses()) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cls.decompile();
|
||||
SaveCode.save(outDir, args, cls.getClassNode());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
File sourcesOutDir;
|
||||
File resOutDir;
|
||||
if (args.isExportAsGradleProject()) {
|
||||
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
|
||||
export.init();
|
||||
sourcesOutDir = export.getSrcOutDir();
|
||||
resOutDir = export.getResOutDir();
|
||||
} else {
|
||||
sourcesOutDir = args.getOutDirSrc();
|
||||
resOutDir = args.getOutDirRes();
|
||||
}
|
||||
if (saveResources) {
|
||||
for (final ResourceFile resourceFile : getResources()) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (ResourceType.isSupportedForUnpack(resourceFile.getType())) {
|
||||
CodeWriter cw = resourceFile.getContent();
|
||||
if (cw != null) {
|
||||
cw.save(new File(outDir, resourceFile.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
appendResourcesSave(executor, resOutDir);
|
||||
}
|
||||
if (saveSources) {
|
||||
appendSourcesSave(executor, sourcesOutDir);
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
private void appendResourcesSave(ExecutorService executor, File outDir) {
|
||||
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
||||
for (ResourceFile resourceFile : getResources()) {
|
||||
if (resourceFile.getType() != ResourceType.ARSC
|
||||
&& inputFileNames.contains(resourceFile.getOriginalName())) {
|
||||
// ignore resource made from input file
|
||||
continue;
|
||||
}
|
||||
executor.execute(new ResourcesSaver(outDir, resourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSourcesSave(ExecutorService executor, File outDir) {
|
||||
Predicate<String> classFilter = args.getClassFilter();
|
||||
for (JavaClass cls : getClasses()) {
|
||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
if (classFilter != null && !classFilter.test(cls.getFullName())) {
|
||||
continue;
|
||||
}
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
ICodeInfo code = cls.getCodeInfo();
|
||||
SaveCode.save(outDir, cls.getClassNode(), code);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error saving class: {}", cls.getFullName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public List<JavaClass> getClasses() {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (classes == null) {
|
||||
List<ClassNode> classNodeList = root.getClasses(false);
|
||||
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
|
||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
clsList.add(new JavaClass(classNode, this));
|
||||
if (!classNode.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
}
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
}
|
||||
@@ -202,7 +267,7 @@ public final class JadxDecompiler {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
resources = new ResourcesLoader(this).load(inputFiles);
|
||||
resources = new ResourcesLoader(this).load();
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
@@ -212,28 +277,19 @@ public final class JadxDecompiler {
|
||||
if (classList.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
|
||||
Map<String, List<JavaClass>> map = new HashMap<>();
|
||||
for (JavaClass javaClass : classList) {
|
||||
String pkg = javaClass.getPackage();
|
||||
List<JavaClass> clsList = map.get(pkg);
|
||||
if (clsList == null) {
|
||||
clsList = new ArrayList<JavaClass>();
|
||||
map.put(pkg, clsList);
|
||||
}
|
||||
List<JavaClass> clsList = map.computeIfAbsent(pkg, k -> new ArrayList<>());
|
||||
clsList.add(javaClass);
|
||||
}
|
||||
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
|
||||
List<JavaPackage> packages = new ArrayList<>(map.size());
|
||||
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
|
||||
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
Collections.sort(packages);
|
||||
for (JavaPackage pkg : packages) {
|
||||
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
|
||||
@Override
|
||||
public int compare(JavaClass o1, JavaClass o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
}
|
||||
return Collections.unmodifiableList(packages);
|
||||
}
|
||||
@@ -245,66 +301,172 @@ public final class JadxDecompiler {
|
||||
return root.getErrorsCounter().getErrorCount();
|
||||
}
|
||||
|
||||
public int getWarnsCount() {
|
||||
if (root == null) {
|
||||
return 0;
|
||||
}
|
||||
return root.getErrorsCounter().getWarnsCount();
|
||||
}
|
||||
|
||||
public void printErrorsReport() {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
root.getClsp().printMissingClasses();
|
||||
root.getErrorsCounter().printReport();
|
||||
}
|
||||
|
||||
void parse() throws DecodeException {
|
||||
reset();
|
||||
init();
|
||||
|
||||
root = new RootNode(args);
|
||||
LOG.info("loading ...");
|
||||
root.load(inputFiles);
|
||||
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.initAppResClass();
|
||||
|
||||
initVisitors();
|
||||
}
|
||||
|
||||
private void initVisitors() {
|
||||
for (IDexTreeVisitor pass : passes) {
|
||||
try {
|
||||
pass.init(root);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processClass(ClassNode cls) {
|
||||
ProcessClass.process(cls, passes, codeGen);
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
public RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
BinaryXMLParser getXmlParser() {
|
||||
synchronized BinaryXMLParser getXmlParser() {
|
||||
if (xmlParser == null) {
|
||||
xmlParser = new BinaryXMLParser(root);
|
||||
}
|
||||
return xmlParser;
|
||||
}
|
||||
|
||||
JavaClass findJavaClass(ClassNode cls) {
|
||||
if (cls == null) {
|
||||
private void loadJavaClass(JavaClass javaClass) {
|
||||
javaClass.getMethods().forEach(mth -> methodsMap.put(mth.getMethodNode(), mth));
|
||||
javaClass.getFields().forEach(fld -> fieldsMap.put(fld.getFieldNode(), fld));
|
||||
|
||||
for (JavaClass innerCls : javaClass.getInnerClasses()) {
|
||||
classesMap.put(innerCls.getClassNode(), innerCls);
|
||||
loadJavaClass(innerCls);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable("For not generated classes")
|
||||
private JavaClass getJavaClassByNode(ClassNode cls) {
|
||||
JavaClass javaClass = classesMap.get(cls);
|
||||
if (javaClass != null) {
|
||||
return javaClass;
|
||||
}
|
||||
// load parent class if inner
|
||||
ClassNode parentClass = cls.getTopParentClass();
|
||||
if (parentClass.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
for (JavaClass javaClass : getClasses()) {
|
||||
if (javaClass.getClassNode().equals(cls)) {
|
||||
if (parentClass != cls) {
|
||||
JavaClass parentJavaClass = classesMap.get(parentClass);
|
||||
if (parentJavaClass == null) {
|
||||
getClasses();
|
||||
parentJavaClass = classesMap.get(parentClass);
|
||||
}
|
||||
loadJavaClass(parentJavaClass);
|
||||
javaClass = classesMap.get(cls);
|
||||
if (javaClass != null) {
|
||||
return javaClass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
// class or parent classes can be excluded from generation
|
||||
if (cls.hasNotGeneratedParent()) {
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("JavaClass not found by ClassNode: " + cls);
|
||||
}
|
||||
|
||||
public IJadxArgs getArgs() {
|
||||
@Nullable
|
||||
private JavaMethod getJavaMethodByNode(MethodNode mth) {
|
||||
JavaMethod javaMethod = methodsMap.get(mth);
|
||||
if (javaMethod != null) {
|
||||
return javaMethod;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = getJavaClassByNode(mth.getParentClass().getTopParentClass());
|
||||
if (javaClass == null) {
|
||||
return null;
|
||||
}
|
||||
loadJavaClass(javaClass);
|
||||
javaMethod = methodsMap.get(mth);
|
||||
if (javaMethod != null) {
|
||||
return javaMethod;
|
||||
}
|
||||
if (mth.getParentClass().hasNotGeneratedParent()) {
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("JavaMethod not found by MethodNode: " + mth);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaField getJavaFieldByNode(FieldNode fld) {
|
||||
JavaField javaField = fieldsMap.get(fld);
|
||||
if (javaField != null) {
|
||||
return javaField;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = getJavaClassByNode(fld.getParentClass().getTopParentClass());
|
||||
if (javaClass == null) {
|
||||
return null;
|
||||
}
|
||||
loadJavaClass(javaClass);
|
||||
javaField = fieldsMap.get(fld);
|
||||
if (javaField != null) {
|
||||
return javaField;
|
||||
}
|
||||
if (fld.getParentClass().hasNotGeneratedParent()) {
|
||||
return null;
|
||||
}
|
||||
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
JavaNode convertNode(Object obj) {
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
}
|
||||
LineAttrNode node = (LineAttrNode) obj;
|
||||
if (node.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
if (obj instanceof ClassNode) {
|
||||
return getJavaClassByNode((ClassNode) obj);
|
||||
}
|
||||
if (obj instanceof MethodNode) {
|
||||
return getJavaMethodByNode(((MethodNode) obj));
|
||||
}
|
||||
if (obj instanceof FieldNode) {
|
||||
return getJavaFieldByNode((FieldNode) obj);
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node type: " + obj);
|
||||
}
|
||||
|
||||
List<JavaNode> convertNodes(Collection<?> nodesList) {
|
||||
return nodesList.stream()
|
||||
.map(this::convertNode)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
|
||||
Map<CodePosition, Object> map = codeInfo.getAnnotations();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
return convertNode(obj);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
||||
JavaClass jCls = javaNode.getTopParentClass();
|
||||
jCls.decompile();
|
||||
int defLine = javaNode.getDecompiledLine();
|
||||
if (defLine == 0) {
|
||||
return null;
|
||||
}
|
||||
return new CodePosition(jCls, defLine, 0);
|
||||
}
|
||||
|
||||
public JadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public final class JavaClass implements JavaNode {
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
@@ -23,6 +24,7 @@ public final class JavaClass implements JavaNode {
|
||||
private List<JavaClass> innerClasses = Collections.emptyList();
|
||||
private List<JavaField> fields = Collections.emptyList();
|
||||
private List<JavaMethod> methods = Collections.emptyList();
|
||||
private boolean listsLoaded;
|
||||
|
||||
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
|
||||
this.decompiler = decompiler;
|
||||
@@ -40,39 +42,56 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
CodeWriter code = cls.getCode();
|
||||
if (code == null) {
|
||||
decompile();
|
||||
code = cls.getCode();
|
||||
}
|
||||
ICodeInfo code = getCodeInfo();
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
return code.toString();
|
||||
return code.getCodeStr();
|
||||
}
|
||||
|
||||
public ICodeInfo getCodeInfo() {
|
||||
return cls.decompile();
|
||||
}
|
||||
|
||||
public void decompile() {
|
||||
if (decompiler == null) {
|
||||
return;
|
||||
}
|
||||
if (cls.getCode() == null) {
|
||||
decompiler.processClass(cls);
|
||||
load();
|
||||
}
|
||||
cls.decompile();
|
||||
}
|
||||
|
||||
ClassNode getClassNode() {
|
||||
public synchronized void reload() {
|
||||
listsLoaded = false;
|
||||
cls.reloadCode();
|
||||
}
|
||||
|
||||
public synchronized String getSmali() {
|
||||
return cls.getSmali();
|
||||
}
|
||||
|
||||
public synchronized void unload() {
|
||||
cls.unload();
|
||||
listsLoaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
public ClassNode getClassNode() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
private void load() {
|
||||
private synchronized void loadLists() {
|
||||
if (listsLoaded) {
|
||||
return;
|
||||
}
|
||||
listsLoaded = true;
|
||||
decompile();
|
||||
|
||||
int inClsCount = cls.getInnerClasses().size();
|
||||
if (inClsCount != 0) {
|
||||
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
|
||||
List<JavaClass> list = new ArrayList<>(inClsCount);
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(inner, this);
|
||||
javaClass.load();
|
||||
javaClass.loadLists();
|
||||
list.add(javaClass);
|
||||
}
|
||||
}
|
||||
@@ -81,10 +100,11 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
int fieldsCount = cls.getFields().size();
|
||||
if (fieldsCount != 0) {
|
||||
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
|
||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||
flds.add(new JavaField(f, this));
|
||||
JavaField javaField = new JavaField(f, this);
|
||||
flds.add(javaField);
|
||||
}
|
||||
}
|
||||
this.fields = Collections.unmodifiableList(flds);
|
||||
@@ -92,63 +112,69 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
int methodsCount = cls.getMethods().size();
|
||||
if (methodsCount != 0) {
|
||||
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
|
||||
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||
mths.add(new JavaMethod(this, m));
|
||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
||||
mths.add(javaMethod);
|
||||
}
|
||||
}
|
||||
Collections.sort(mths, new Comparator<JavaMethod>() {
|
||||
@Override
|
||||
public int compare(JavaMethod o1, JavaMethod o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||
this.methods = Collections.unmodifiableList(mths);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
decompile();
|
||||
return cls.getCode().getAnnotations();
|
||||
protected JadxDecompiler getRootDecompiler() {
|
||||
if (parent != null) {
|
||||
return parent.getRootDecompiler();
|
||||
}
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
ICodeInfo code = getCodeInfo();
|
||||
if (code == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return code.getAnnotations();
|
||||
}
|
||||
|
||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
|
||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||
CodePosition codePosition = entry.getKey();
|
||||
Object obj = entry.getValue();
|
||||
JavaNode node = getRootDecompiler().convertNode(obj);
|
||||
if (node != null) {
|
||||
resultMap.put(codePosition, node);
|
||||
}
|
||||
}
|
||||
ClassNode clsNode = null;
|
||||
if (obj instanceof ClassNode) {
|
||||
clsNode = (ClassNode) obj;
|
||||
} else if (obj instanceof MethodNode) {
|
||||
clsNode = ((MethodNode) obj).getParentClass();
|
||||
} else if (obj instanceof FieldNode) {
|
||||
clsNode = ((FieldNode) obj).getParentClass();
|
||||
}
|
||||
if (clsNode == null) {
|
||||
return null;
|
||||
}
|
||||
clsNode = clsNode.getTopParentClass();
|
||||
JavaClass jCls = decompiler.findJavaClass(clsNode);
|
||||
if (jCls == null) {
|
||||
return null;
|
||||
}
|
||||
jCls.decompile();
|
||||
int defLine = ((LineAttrNode) obj).getDecompiledLine();
|
||||
if (defLine == 0) {
|
||||
return null;
|
||||
}
|
||||
return new CodePosition(jCls, defLine, 0);
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return getRootDecompiler().convertNodes(cls.getUseIn());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public CodePosition getDefinitionPosition() {
|
||||
return getRootDecompiler().getDefinitionPosition(this);
|
||||
}
|
||||
|
||||
public Integer getSourceLine(int decompiledLine) {
|
||||
decompile();
|
||||
return cls.getCode().getLineMapping().get(decompiledLine);
|
||||
return getCodeInfo().getLineMapping().get(decompiledLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -161,6 +187,10 @@ public final class JavaClass implements JavaNode {
|
||||
return cls.getFullName();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return cls.getRawName();
|
||||
}
|
||||
|
||||
public String getPackage() {
|
||||
return cls.getPackage();
|
||||
}
|
||||
@@ -170,25 +200,31 @@ public final class JavaClass implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent == null ? this : parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessInfo() {
|
||||
return cls.getAccessFlags();
|
||||
}
|
||||
|
||||
public List<JavaClass> getInnerClasses() {
|
||||
decompile();
|
||||
loadLists();
|
||||
return innerClasses;
|
||||
}
|
||||
|
||||
public List<JavaField> getFields() {
|
||||
decompile();
|
||||
loadLists();
|
||||
return fields;
|
||||
}
|
||||
|
||||
public List<JavaMethod> getMethods() {
|
||||
decompile();
|
||||
loadLists();
|
||||
return methods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return cls.getDecompiledLine();
|
||||
}
|
||||
@@ -205,6 +241,6 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return cls.getFullName() + "[ " + getFullName() + " ]";
|
||||
return getFullName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -21,7 +23,7 @@ public final class JavaField implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return parent.getFullName() + "." + getName();
|
||||
return parent.getFullName() + '.' + getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,15 +31,48 @@ public final class JavaField implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return field.getAccessFlags();
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return field.getType();
|
||||
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
public FieldNode getFieldNode() {
|
||||
return field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return field.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaField && field.equals(((JavaField) o).field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return field.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import java.util.List;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public final class JavaMethod implements JavaNode {
|
||||
private final MethodNode mth;
|
||||
@@ -30,16 +32,33 @@ public final class JavaMethod implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return mth.getAccessFlags();
|
||||
}
|
||||
|
||||
public List<ArgType> getArguments() {
|
||||
return mth.getMethodInfo().getArgumentsTypes();
|
||||
List<ArgType> infoArgTypes = mth.getMethodInfo().getArgumentsTypes();
|
||||
if (infoArgTypes.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> arguments = mth.getArgTypes();
|
||||
return Utils.collectionMap(arguments,
|
||||
type -> ArgType.tryToResolveClassAlias(mth.root(), type));
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
return mth.getReturnType();
|
||||
ArgType retType = mth.getReturnType();
|
||||
return ArgType.tryToResolveClassAlias(mth.root(), retType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
@@ -50,7 +69,30 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getMethodInfo().isClassInit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
public MethodNode getMethodNode() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mth.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaMethod && mth.equals(((JavaMethod) o).mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mth.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface JavaNode {
|
||||
|
||||
String getName();
|
||||
@@ -7,4 +9,10 @@ public interface JavaNode {
|
||||
String getFullName();
|
||||
|
||||
JavaClass getDeclaringClass();
|
||||
|
||||
JavaClass getTopParentClass();
|
||||
|
||||
int getDecompiledLine();
|
||||
|
||||
List<JavaNode> getUseIn();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -33,6 +34,21 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||
|
||||
public class ResourceFile {
|
||||
|
||||
public static final class ZipRef {
|
||||
@@ -33,22 +35,34 @@ public class ResourceFile {
|
||||
private final String name;
|
||||
private final ResourceType type;
|
||||
private ZipRef zipRef;
|
||||
private String deobfName;
|
||||
|
||||
ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
if (!ZipSecurity.isValidZipEntryName(name)) {
|
||||
return null;
|
||||
}
|
||||
return new ResourceFile(decompiler, name, type);
|
||||
}
|
||||
|
||||
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
this.decompiler = decompiler;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
public String getOriginalName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDeobfName() {
|
||||
return deobfName != null ? deobfName : name;
|
||||
}
|
||||
|
||||
public ResourceType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public CodeWriter getContent() {
|
||||
public ResContainer loadContent() {
|
||||
return ResourcesLoader.loadContent(decompiler, this);
|
||||
}
|
||||
|
||||
@@ -56,12 +70,21 @@ public class ResourceFile {
|
||||
this.zipRef = zipRef;
|
||||
}
|
||||
|
||||
ZipRef getZipRef() {
|
||||
public void setAlias(ResourceEntry ri) {
|
||||
int index = name.lastIndexOf('.');
|
||||
deobfName = String.format("res/%s%s/%s%s",
|
||||
ri.getTypeName(),
|
||||
ri.getConfig(),
|
||||
ri.getKeyName(),
|
||||
index == -1 ? "" : name.substring(index));
|
||||
}
|
||||
|
||||
public ZipRef getZipRef() {
|
||||
return zipRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
|
||||
return "ResourceFile{name='" + name + '\'' + ", type=" + type + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ResourceFileContent extends ResourceFile {
|
||||
private final ICodeInfo content;
|
||||
|
||||
public ResourceFileContent(String name, ResourceType type, ICodeInfo content) {
|
||||
super(null, name, type);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResContainer loadContent() {
|
||||
return ResContainer.textResource(getDeobfName(), content);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,20 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(".dex", ".class"),
|
||||
MANIFEST("AndroidManifest.xml"),
|
||||
XML(".xml"), // TODO binary or not?
|
||||
ARSC(".arsc"), // TODO decompile !!!
|
||||
FONT(".ttf"),
|
||||
CODE(".dex", ".jar", ".class"),
|
||||
XML(".xml"),
|
||||
ARSC(".arsc"),
|
||||
FONT(".ttf", ".otf"),
|
||||
IMG(".png", ".gif", ".jpg"),
|
||||
MEDIA(".mp3", ".wav"),
|
||||
LIB(".so"),
|
||||
MANIFEST,
|
||||
UNKNOWN;
|
||||
|
||||
private final String[] exts;
|
||||
@@ -20,31 +27,31 @@ public enum ResourceType {
|
||||
return exts;
|
||||
}
|
||||
|
||||
public static ResourceType getFileType(String fileName) {
|
||||
private static final Map<String, ResourceType> EXT_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (ResourceType type : ResourceType.values()) {
|
||||
for (String ext : type.getExts()) {
|
||||
if (fileName.endsWith(ext)) {
|
||||
return type;
|
||||
ResourceType prev = EXT_MAP.put(ext, type);
|
||||
if (prev != null) {
|
||||
throw new JadxRuntimeException("Duplicate extension in ResourceType: " + ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ResourceType getFileType(String fileName) {
|
||||
int dot = fileName.lastIndexOf('.');
|
||||
if (dot != -1) {
|
||||
String ext = fileName.substring(dot).toLowerCase(Locale.ROOT);
|
||||
ResourceType resType = EXT_MAP.get(ext);
|
||||
if (resType != null) {
|
||||
if (resType == XML && fileName.equals("AndroidManifest.xml")) {
|
||||
return MANIFEST;
|
||||
}
|
||||
return resType;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
public static boolean isSupportedForUnpack(ResourceType type) {
|
||||
switch (type) {
|
||||
case CODE:
|
||||
case ARSC:
|
||||
case LIB:
|
||||
case FONT:
|
||||
case IMG:
|
||||
case UNKNOWN:
|
||||
return false;
|
||||
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
@@ -21,118 +14,132 @@ import java.util.zip.ZipFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.android.Res9patchStreamDecoder;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
|
||||
import static jadx.core.utils.files.FileUtils.copyStream;
|
||||
|
||||
// TODO: move to core package
|
||||
public final class ResourcesLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||
|
||||
private static final int READ_BUFFER_SIZE = 8 * 1024;
|
||||
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||
|
||||
private final JadxDecompiler jadxRef;
|
||||
|
||||
ResourcesLoader(JadxDecompiler jadxRef) {
|
||||
this.jadxRef = jadxRef;
|
||||
}
|
||||
|
||||
List<ResourceFile> load(List<InputFile> inputFiles) {
|
||||
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
|
||||
for (InputFile file : inputFiles) {
|
||||
loadFile(list, file.getFile());
|
||||
List<ResourceFile> load() {
|
||||
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
|
||||
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
||||
for (File file : inputFiles) {
|
||||
loadFile(list, file);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public interface ResourceDecoder {
|
||||
Object decode(long size, InputStream is) throws IOException;
|
||||
public interface ResourceDecoder<T> {
|
||||
T decode(long size, InputStream is) throws IOException;
|
||||
}
|
||||
|
||||
public static Object decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
return null;
|
||||
}
|
||||
ZipFile zipFile = null;
|
||||
InputStream inputStream = null;
|
||||
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
|
||||
try {
|
||||
zipFile = new ZipFile(zipRef.getZipFile());
|
||||
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
||||
if (entry == null) {
|
||||
throw new IOException("Zip entry not found: " + zipRef);
|
||||
}
|
||||
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
|
||||
return decoder.decode(entry.getSize(), inputStream);
|
||||
} catch (Exception e) {
|
||||
throw new JadxException("Error decode: " + zipRef.getEntryName(), e);
|
||||
} finally {
|
||||
try {
|
||||
if (zipFile != null) {
|
||||
zipFile.close();
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
File file = new File(rf.getOriginalName());
|
||||
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
return decoder.decode(file.length(), inputStream);
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error close zip file: {}", zipRef, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CodeWriter loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
|
||||
try {
|
||||
return (CodeWriter) decodeStream(rf, new ResourceDecoder() {
|
||||
@Override
|
||||
public Object decode(long size, InputStream is) throws IOException {
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return new CodeWriter().add("File too big, size: "
|
||||
+ String.format("%.2f KB", size / 1024.));
|
||||
} else {
|
||||
try (ZipFile zipFile = new ZipFile(zipRef.getZipFile())) {
|
||||
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
||||
if (entry == null) {
|
||||
throw new IOException("Zip entry not found: " + zipRef);
|
||||
}
|
||||
if (!ZipSecurity.isValidZipEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream inputStream = ZipSecurity.getInputStreamForEntry(zipFile, entry)) {
|
||||
return decoder.decode(entry.getSize(), inputStream);
|
||||
}
|
||||
return loadContent(jadxRef, rf.getType(), is);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JadxException("Error decode: " + rf.getDeobfName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
|
||||
try {
|
||||
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
|
||||
} catch (JadxException e) {
|
||||
LOG.error("Decode error", e);
|
||||
CodeWriter cw = new CodeWriter();
|
||||
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
||||
cw.startLine(Utils.getStackTrace(e.getCause()));
|
||||
return cw;
|
||||
Utils.appendStackTrace(cw, e.getCause());
|
||||
return ResContainer.textResource(rf.getDeobfName(), cw.finish());
|
||||
}
|
||||
}
|
||||
|
||||
private static CodeWriter loadContent(JadxDecompiler jadxRef, ResourceType type,
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream) throws IOException {
|
||||
switch (type) {
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
return jadxRef.getXmlParser().parse(inputStream);
|
||||
ICodeInfo content = jadxRef.getXmlParser().parse(inputStream);
|
||||
return ResContainer.textResource(rf.getDeobfName(), content);
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser().decodeToCodeWriter(inputStream);
|
||||
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
|
||||
|
||||
case IMG:
|
||||
return decodeImage(rf, inputStream);
|
||||
|
||||
default:
|
||||
return ResContainer.resourceFileLink(rf);
|
||||
}
|
||||
return loadToCodeWriter(inputStream);
|
||||
}
|
||||
|
||||
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
|
||||
String name = rf.getOriginalName();
|
||||
if (name.endsWith(".9.png")) {
|
||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
decoder.decode(inputStream, os);
|
||||
return ResContainer.decodedData(rf.getDeobfName(), os.toByteArray());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||
}
|
||||
}
|
||||
return ResContainer.resourceFileLink(rf);
|
||||
}
|
||||
|
||||
private void loadFile(List<ResourceFile> list, File file) {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
ZipFile zip = null;
|
||||
try {
|
||||
zip = new ZipFile(file);
|
||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
addEntry(list, file, entry);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
|
||||
} finally {
|
||||
if (zip != null) {
|
||||
try {
|
||||
zip.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Zip file close error: {}", file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
if (FileUtils.isZipFile(file)) {
|
||||
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> addEntry(list, file, entry));
|
||||
} else {
|
||||
addResourceFile(list, file);
|
||||
}
|
||||
}
|
||||
|
||||
private void addResourceFile(List<ResourceFile> list, File file) {
|
||||
String name = file.getAbsolutePath();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
list.add(rf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,28 +149,16 @@ public final class ResourcesLoader {
|
||||
}
|
||||
String name = entry.getName();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = new ResourceFile(jadxRef, name, type);
|
||||
rf.setZipRef(new ZipRef(zipFile, name));
|
||||
list.add(rf);
|
||||
// LOG.debug("Add resource entry: {}, size: {}", name, entry.getSize());
|
||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
rf.setZipRef(new ZipRef(zipFile, name));
|
||||
list.add(rf);
|
||||
}
|
||||
}
|
||||
|
||||
private static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||
byte[] buffer = new byte[READ_BUFFER_SIZE];
|
||||
int count;
|
||||
try {
|
||||
while ((count = is.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, count);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
cw.add(baos.toString("UTF-8"));
|
||||
return cw;
|
||||
copyStream(is, baos);
|
||||
return new SimpleCodeInfo(baos.toString("UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeInfo;
|
||||
|
||||
public class InMemoryCodeCache implements ICodeCache {
|
||||
|
||||
private final Map<String, ICodeInfo> storage = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||
storage.put(clsFullName, codeInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String clsFullName) {
|
||||
storage.remove(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeInfo get(String clsFullName) {
|
||||
return storage.get(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InMemoryCodeCache";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeInfo;
|
||||
|
||||
public class NoOpCodeCache implements ICodeCache {
|
||||
|
||||
@Override
|
||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String clsFullName) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeInfo get(String clsFullName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NoOpCodeCache";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
|
||||
public class SimpleCodeInfo implements ICodeInfo {
|
||||
|
||||
private final String code;
|
||||
private final Map<Integer, Integer> lineMapping;
|
||||
private final Map<CodePosition, Object> annotations;
|
||||
|
||||
public SimpleCodeInfo(String code) {
|
||||
this(code, Collections.emptyMap(), Collections.emptyMap());
|
||||
}
|
||||
|
||||
public SimpleCodeInfo(ICodeInfo codeInfo) {
|
||||
this(codeInfo.getCodeStr(), codeInfo.getLineMapping(), codeInfo.getAnnotations());
|
||||
}
|
||||
|
||||
public SimpleCodeInfo(String code, Map<Integer, Integer> lineMapping, Map<CodePosition, Object> annotations) {
|
||||
this.code = code;
|
||||
this.lineMapping = lineMapping;
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCodeStr() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lineMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,31 @@ package jadx.core;
|
||||
|
||||
public class Consts {
|
||||
public static final boolean DEBUG = false;
|
||||
public static final boolean DEBUG_WITH_ERRORS = false; // TODO: fix errors
|
||||
public static final boolean DEBUG_USAGE = false;
|
||||
public static final boolean DEBUG_TYPE_INFERENCE = false;
|
||||
public static final boolean DEBUG_OVERLOADED_CASTS = false;
|
||||
|
||||
public static final String CLASS_OBJECT = "java.lang.Object";
|
||||
public static final String CLASS_STRING = "java.lang.String";
|
||||
public static final String CLASS_CLASS = "java.lang.Class";
|
||||
public static final String CLASS_THROWABLE = "java.lang.Throwable";
|
||||
public static final String CLASS_EXCEPTION = "java.lang.Exception";
|
||||
public static final String CLASS_ENUM = "java.lang.Enum";
|
||||
|
||||
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
|
||||
|
||||
public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
|
||||
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
|
||||
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
|
||||
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
|
||||
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
|
||||
public static final String DALVIK_ANNOTATION_PKG = "Ldalvik/annotation/";
|
||||
public static final String DALVIK_SIGNATURE = "Ldalvik/annotation/Signature;";
|
||||
public static final String DALVIK_INNER_CLASS = "Ldalvik/annotation/InnerClass;";
|
||||
public static final String DALVIK_THROWS = "Ldalvik/annotation/Throws;";
|
||||
public static final String DALVIK_ANNOTATION_DEFAULT = "Ldalvik/annotation/AnnotationDefault;";
|
||||
|
||||
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
|
||||
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
||||
|
||||
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
|
||||
|
||||
private Consts() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,6 @@
|
||||
package jadx.core;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
import jadx.core.dex.visitors.CodeShrinker;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
import jadx.core.dex.visitors.DebugInfoVisitor;
|
||||
import jadx.core.dex.visitors.DependencyCollector;
|
||||
import jadx.core.dex.visitors.DotGraphVisitor;
|
||||
import jadx.core.dex.visitors.EnumVisitor;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.ReSugarCode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.ProcessVariables;
|
||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInference;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
@@ -43,86 +10,166 @@ import java.util.jar.Manifest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||
import jadx.core.dex.visitors.AttachTryCatchVisitor;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
import jadx.core.dex.visitors.ConstructorVisitor;
|
||||
import jadx.core.dex.visitors.DeboxingVisitor;
|
||||
import jadx.core.dex.visitors.DotGraphVisitor;
|
||||
import jadx.core.dex.visitors.EnumVisitor;
|
||||
import jadx.core.dex.visitors.ExtractFieldInit;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.dex.visitors.FixAccessModifiers;
|
||||
import jadx.core.dex.visitors.GenericTypesVisitor;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.InlineMethods;
|
||||
import jadx.core.dex.visitors.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.MarkMethodsForInline;
|
||||
import jadx.core.dex.visitors.MethodInvokeVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.MoveInlineVisitor;
|
||||
import jadx.core.dex.visitors.OverrideMethodVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
|
||||
import jadx.core.dex.visitors.ReSugarCode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.ShadowFieldVisitor;
|
||||
import jadx.core.dex.visitors.SignatureProcessor;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
|
||||
|
||||
public class Jadx {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||
|
||||
private Jadx() {
|
||||
}
|
||||
|
||||
static {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("debug enabled");
|
||||
}
|
||||
if (Jadx.class.desiredAssertionStatus()) {
|
||||
LOG.info("assertions enabled");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
|
||||
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>(3);
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
passes.add(new FallbackModeVisitor());
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new SignatureProcessor());
|
||||
passes.add(new RenameVisitor());
|
||||
passes.add(new UsageInfoVisitor());
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
if (args.isFallbackMode()) {
|
||||
passes.add(new FallbackModeVisitor());
|
||||
} else {
|
||||
passes.add(new BlockSplitter());
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinallyExtract());
|
||||
passes.add(new BlockFinish());
|
||||
return getFallbackPassesList();
|
||||
}
|
||||
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new DebugInfoVisitor());
|
||||
passes.add(new TypeInference());
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoAttachVisitor());
|
||||
}
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw(outDir));
|
||||
}
|
||||
passes.add(new BlockSplitter());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinish());
|
||||
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new FinishTypeInference());
|
||||
passes.add(new EliminatePhiNodes());
|
||||
passes.add(new AttachMethodDetails());
|
||||
passes.add(new OverrideMethodVisitor());
|
||||
|
||||
passes.add(new ModVisitor());
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new MoveInlineVisitor());
|
||||
passes.add(new ConstructorVisitor());
|
||||
passes.add(new InitCodeVariables());
|
||||
passes.add(new MarkFinallyVisitor());
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new TypeInferenceVisitor());
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new ReSugarCode());
|
||||
passes.add(new InlineMethods());
|
||||
passes.add(new GenericTypesVisitor());
|
||||
passes.add(new ShadowFieldVisitor());
|
||||
passes.add(new DeboxingVisitor());
|
||||
passes.add(new ModVisitor());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new ReSugarCode());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dump());
|
||||
}
|
||||
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dump(outDir));
|
||||
}
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CleanRegions());
|
||||
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new MethodInvokeVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions(outDir));
|
||||
}
|
||||
passes.add(new MarkMethodsForInline());
|
||||
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
|
||||
passes.add(new DependencyCollector());
|
||||
|
||||
passes.add(new RenameVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
try {
|
||||
ClassLoader classLoader = Utils.class.getClassLoader();
|
||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
Manifest manifest = new Manifest(resources.nextElement().openStream());
|
||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||
if (ver != null) {
|
||||
return ver;
|
||||
try (InputStream is = resources.nextElement().openStream()) {
|
||||
Manifest manifest = new Manifest(is);
|
||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||
if (ver != null) {
|
||||
return ver;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,97 @@
|
||||
package jadx.core;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.LoadStage;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.GENERATED;
|
||||
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
|
||||
import static jadx.core.dex.nodes.ProcessState.STARTED;
|
||||
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
|
||||
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
|
||||
|
||||
public final class ProcessClass {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
|
||||
|
||||
private ProcessClass() {
|
||||
}
|
||||
|
||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
|
||||
if (codeGen == null && cls.getState() == PROCESSED) {
|
||||
return;
|
||||
@Nullable
|
||||
private static ICodeInfo process(ClassNode cls, boolean codegen) {
|
||||
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
||||
// nothing to do
|
||||
return null;
|
||||
}
|
||||
synchronized (cls) {
|
||||
synchronized (cls.getClassInfo()) {
|
||||
try {
|
||||
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
||||
cls.unload();
|
||||
cls.deepUnload();
|
||||
cls.root().runPreDecompileStageForClass(cls);
|
||||
}
|
||||
if (codegen) {
|
||||
if (cls.getState() == GENERATED_AND_UNLOADED) {
|
||||
// allow to run code generation again
|
||||
cls.setState(NOT_LOADED);
|
||||
}
|
||||
cls.setLoadStage(LoadStage.CODEGEN_STAGE);
|
||||
if (cls.contains(AFlag.RELOAD_AT_CODEGEN_STAGE)) {
|
||||
cls.remove(AFlag.RELOAD_AT_CODEGEN_STAGE);
|
||||
cls.unload();
|
||||
}
|
||||
} else {
|
||||
cls.setLoadStage(LoadStage.PROCESS_STAGE);
|
||||
}
|
||||
if (cls.getState() == NOT_LOADED) {
|
||||
cls.load();
|
||||
cls.setState(STARTED);
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
}
|
||||
if (cls.getState() == LOADED) {
|
||||
cls.setState(PROCESS_STARTED);
|
||||
for (IDexTreeVisitor visitor : cls.root().getPasses()) {
|
||||
DepthTraversal.visit(visitor, cls);
|
||||
}
|
||||
cls.setState(PROCESSED);
|
||||
cls.setState(PROCESS_COMPLETE);
|
||||
}
|
||||
if (cls.getState() == PROCESSED && codeGen != null) {
|
||||
processDependencies(cls, passes);
|
||||
codeGen.visit(cls);
|
||||
cls.setState(GENERATED);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
||||
} finally {
|
||||
if (cls.getState() == GENERATED) {
|
||||
cls.unload();
|
||||
cls.setState(UNLOADED);
|
||||
if (codegen) {
|
||||
ICodeInfo code = CodeGen.generate(cls);
|
||||
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
|
||||
cls.unload();
|
||||
cls.setState(GENERATED_AND_UNLOADED);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
for (ClassNode depCls : cls.getDependencies()) {
|
||||
process(depCls, passes, null);
|
||||
@NotNull
|
||||
public static ICodeInfo generateCode(ClassNode cls) {
|
||||
ClassNode topParentClass = cls.getTopParentClass();
|
||||
if (topParentClass != cls) {
|
||||
return generateCode(topParentClass);
|
||||
}
|
||||
try {
|
||||
for (ClassNode depCls : cls.getDependencies()) {
|
||||
process(depCls, false);
|
||||
}
|
||||
ICodeInfo code = process(cls, true);
|
||||
if (code == null) {
|
||||
throw new JadxRuntimeException("Codegen failed");
|
||||
}
|
||||
return code;
|
||||
} catch (Throwable e) {
|
||||
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.utils.ZipSecurity;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import static jadx.core.utils.Utils.notEmpty;
|
||||
|
||||
/**
|
||||
* Classes list for import into classpath graph
|
||||
*/
|
||||
@@ -38,158 +52,300 @@ public class ClsSet {
|
||||
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
|
||||
|
||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||
private static final int VERSION = 1;
|
||||
private static final int VERSION = 3;
|
||||
|
||||
private static final String STRING_CHARSET = "US-ASCII";
|
||||
|
||||
private NClass[] classes;
|
||||
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
|
||||
|
||||
public void load(RootNode root) {
|
||||
private final RootNode root;
|
||||
|
||||
public ClsSet(RootNode root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
private enum TypeEnum {
|
||||
WILDCARD,
|
||||
GENERIC,
|
||||
GENERIC_TYPE_VARIABLE,
|
||||
OUTER_GENERIC,
|
||||
OBJECT,
|
||||
ARRAY,
|
||||
PRIMITIVE
|
||||
}
|
||||
|
||||
private ClspClass[] classes;
|
||||
|
||||
public void loadFromClstFile() throws IOException, DecodeException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
|
||||
if (input == null) {
|
||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
||||
}
|
||||
load(input);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
|
||||
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadFrom(RootNode root) {
|
||||
List<ClassNode> list = root.getClasses(true);
|
||||
Map<String, NClass> names = new HashMap<String, NClass>(list.size());
|
||||
Map<String, ClspClass> names = new HashMap<>(list.size());
|
||||
int k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
String clsRawName = cls.getRawName();
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
NClass nClass = new NClass(clsRawName, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
} else {
|
||||
names.put(clsRawName, null);
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String clsRawName = clsType.getObject();
|
||||
cls.load();
|
||||
ClspClass nClass = new ClspClass(clsType, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
nClass.setTypeParameters(cls.getGenericTypeParameters());
|
||||
nClass.setMethods(getMethodsDetails(cls));
|
||||
}
|
||||
classes = new NClass[k];
|
||||
classes = new ClspClass[k];
|
||||
k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
NClass nClass = getCls(cls.getRawName(), names);
|
||||
if (nClass == null) {
|
||||
throw new JadxRuntimeException("Missing class: " + cls);
|
||||
}
|
||||
nClass.setParents(makeParentsArray(cls, names));
|
||||
classes[k] = nClass;
|
||||
k++;
|
||||
ClspClass nClass = getCls(cls, names);
|
||||
if (nClass == null) {
|
||||
throw new JadxRuntimeException("Missing class: " + cls);
|
||||
}
|
||||
nClass.setParents(makeParentsArray(cls));
|
||||
classes[k] = nClass;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
|
||||
private List<ClspMethod> getMethodsDetails(ClassNode cls) {
|
||||
List<MethodNode> methodsList = cls.getMethods();
|
||||
List<ClspMethod> methods = new ArrayList<>(methodsList.size());
|
||||
for (MethodNode mth : methodsList) {
|
||||
processMethodDetails(mth, methods);
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private void processMethodDetails(MethodNode mth, List<ClspMethod> methods) {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isPrivate()) {
|
||||
return;
|
||||
}
|
||||
ArgType genericRetType = mth.getReturnType();
|
||||
boolean varArgs = accessFlags.isVarArgs();
|
||||
List<ArgType> throwList = mth.getThrows();
|
||||
List<ArgType> typeParameters = mth.getTypeParameters();
|
||||
// add only methods with additional info
|
||||
if (varArgs
|
||||
|| notEmpty(throwList)
|
||||
|| notEmpty(typeParameters)
|
||||
|| genericRetType.containsGeneric()
|
||||
|| mth.containsGenericArgs()
|
||||
|| mth.isArgsOverloaded()) {
|
||||
ClspMethod clspMethod = new ClspMethod(mth.getMethodInfo(),
|
||||
mth.getArgTypes(), genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
methods.add(clspMethod);
|
||||
}
|
||||
}
|
||||
|
||||
public static ArgType[] makeParentsArray(ClassNode cls) {
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
NClass c = getCls(superClass.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
if (superClass == null) {
|
||||
// cls is java.lang.Object
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
|
||||
parents[0] = superClass;
|
||||
int k = 1;
|
||||
for (ArgType iface : cls.getInterfaces()) {
|
||||
NClass c = getCls(iface.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
parents[k] = iface;
|
||||
k++;
|
||||
}
|
||||
return parents.toArray(new NClass[parents.size()]);
|
||||
return parents;
|
||||
}
|
||||
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
NClass id = names.get(fullName);
|
||||
if (id == null && !names.containsKey(fullName)) {
|
||||
private static ClspClass getCls(ClassNode cls, Map<String, ClspClass> names) {
|
||||
return getCls(cls.getRawName(), names);
|
||||
}
|
||||
|
||||
private static ClspClass getCls(ArgType clsType, Map<String, ClspClass> names) {
|
||||
return getCls(clsType.getObject(), names);
|
||||
}
|
||||
|
||||
private static ClspClass getCls(String fullName, Map<String, ClspClass> names) {
|
||||
ClspClass cls = names.get(fullName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Class not found: {}", fullName);
|
||||
}
|
||||
return id;
|
||||
return cls;
|
||||
}
|
||||
|
||||
void save(File output) throws IOException {
|
||||
FileUtils.makeDirsForFile(output);
|
||||
|
||||
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
|
||||
try {
|
||||
String outputName = output.getName();
|
||||
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||
public void save(Path path) throws IOException {
|
||||
FileUtils.makeDirsForFile(path);
|
||||
String outputName = path.getFileName().toString();
|
||||
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||
try (BufferedOutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(path))) {
|
||||
save(outputStream);
|
||||
} else if (outputName.endsWith(".jar")) {
|
||||
ZipOutputStream out = new ZipOutputStream(outputStream);
|
||||
try {
|
||||
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
|
||||
save(out);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
}
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
} else if (outputName.endsWith(".jar")) {
|
||||
Path temp = FileUtils.createTempFile(".zip");
|
||||
Files.copy(path, temp, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
public void save(OutputStream output) throws IOException {
|
||||
DataOutputStream out = new DataOutputStream(output);
|
||||
try {
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
LOG.info("Classes count: {}", classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
}
|
||||
for (NClass cls : classes) {
|
||||
NClass[] parents = cls.getParents();
|
||||
out.writeByte(parents.length);
|
||||
for (NClass parent : parents) {
|
||||
out.writeInt(parent.getId());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
InputStream input = getClass().getResourceAsStream(CLST_FILENAME);
|
||||
if (input == null) {
|
||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
||||
}
|
||||
try {
|
||||
load(input);
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void load(File input) throws IOException, DecodeException {
|
||||
String name = input.getName();
|
||||
InputStream inputStream = new FileInputStream(input);
|
||||
try {
|
||||
if (name.endsWith(CLST_EXTENSION)) {
|
||||
load(inputStream);
|
||||
} else if (name.endsWith(".jar")) {
|
||||
ZipInputStream in = new ZipInputStream(inputStream);
|
||||
try {
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (entry.getName().endsWith(CLST_EXTENSION)) {
|
||||
load(in);
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
|
||||
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
|
||||
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
|
||||
boolean clstReplaced = false;
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
String entryName = entry.getName();
|
||||
ZipEntry copyEntry = new ZipEntry(entryName);
|
||||
copyEntry.setLastModifiedTime(entry.getLastModifiedTime()); // preserve modified time
|
||||
out.putNextEntry(copyEntry);
|
||||
if (entryName.equals(clst)) {
|
||||
save(out);
|
||||
clstReplaced = true;
|
||||
} else {
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
if (!clstReplaced) {
|
||||
out.putNextEntry(new ZipEntry(clst));
|
||||
save(out);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
}
|
||||
}
|
||||
|
||||
public void load(InputStream input) throws IOException, DecodeException {
|
||||
DataInputStream in = new DataInputStream(input);
|
||||
try {
|
||||
private void save(OutputStream output) throws IOException {
|
||||
DataOutputStream out = new DataOutputStream(output);
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
Map<String, ClspClass> names = new HashMap<>(classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (ClspClass cls : classes) {
|
||||
String clsName = cls.getName();
|
||||
writeString(out, clsName);
|
||||
names.put(clsName, cls);
|
||||
}
|
||||
for (ClspClass cls : classes) {
|
||||
writeArgTypesArray(out, cls.getParents(), names);
|
||||
writeArgTypesList(out, cls.getTypeParameters(), names);
|
||||
List<ClspMethod> methods = cls.getSortedMethodsList();
|
||||
out.writeShort(methods.size());
|
||||
for (ClspMethod method : methods) {
|
||||
writeMethod(out, method, names);
|
||||
}
|
||||
}
|
||||
int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum();
|
||||
LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size());
|
||||
}
|
||||
|
||||
private static void writeMethod(DataOutputStream out, ClspMethod method, Map<String, ClspClass> names) throws IOException {
|
||||
MethodInfo methodInfo = method.getMethodInfo();
|
||||
writeString(out, methodInfo.getName());
|
||||
writeArgTypesList(out, methodInfo.getArgumentsTypes(), names);
|
||||
writeArgType(out, methodInfo.getReturnType(), names);
|
||||
|
||||
writeArgTypesList(out, method.containsGenericArgs() ? method.getArgTypes() : Collections.emptyList(), names);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
writeArgTypesList(out, method.getTypeParameters(), names);
|
||||
out.writeBoolean(method.isVarArg());
|
||||
writeArgTypesList(out, method.getThrows(), names);
|
||||
}
|
||||
|
||||
private static void writeArgTypesList(DataOutputStream out, List<ArgType> list, Map<String, ClspClass> names) throws IOException {
|
||||
int size = list.size();
|
||||
writeUnsignedByte(out, size);
|
||||
if (size != 0) {
|
||||
for (ArgType type : list) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeArgTypesArray(DataOutputStream out, @Nullable ArgType[] arr, Map<String, ClspClass> names) throws IOException {
|
||||
if (arr == null) {
|
||||
out.writeByte(-1);
|
||||
return;
|
||||
}
|
||||
int size = arr.length;
|
||||
out.writeByte(size);
|
||||
if (size != 0) {
|
||||
for (ArgType type : arr) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, ClspClass> names) throws IOException {
|
||||
if (argType == null) {
|
||||
out.writeByte(-1);
|
||||
return;
|
||||
}
|
||||
if (argType.isPrimitive()) {
|
||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||
} else if (argType.getOuterType() != null) {
|
||||
out.writeByte(TypeEnum.OUTER_GENERIC.ordinal());
|
||||
writeArgType(out, argType.getOuterType(), names);
|
||||
writeArgType(out, argType.getInnerType(), names);
|
||||
} else if (argType.getWildcardType() != null) {
|
||||
out.writeByte(TypeEnum.WILDCARD.ordinal());
|
||||
ArgType.WildcardBound bound = argType.getWildcardBound();
|
||||
out.writeByte(bound.getNum());
|
||||
if (bound != ArgType.WildcardBound.UNBOUND) {
|
||||
writeArgType(out, argType.getWildcardType(), names);
|
||||
}
|
||||
} else if (argType.isGeneric()) {
|
||||
out.writeByte(TypeEnum.GENERIC.ordinal());
|
||||
out.writeInt(getCls(argType, names).getId());
|
||||
writeArgTypesList(out, argType.getGenericTypes(), names);
|
||||
} else if (argType.isGenericType()) {
|
||||
out.writeByte(TypeEnum.GENERIC_TYPE_VARIABLE.ordinal());
|
||||
writeString(out, argType.getObject());
|
||||
writeArgTypesList(out, argType.getExtendTypes(), names);
|
||||
} else if (argType.isObject()) {
|
||||
out.writeByte(TypeEnum.OBJECT.ordinal());
|
||||
out.writeInt(getCls(argType, names).getId());
|
||||
} else if (argType.isArray()) {
|
||||
out.writeByte(TypeEnum.ARRAY.ordinal());
|
||||
writeArgType(out, argType.getArrayElement(), names);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Cannot save type: " + argType);
|
||||
}
|
||||
}
|
||||
|
||||
private void load(File input) throws IOException, DecodeException {
|
||||
String name = input.getName();
|
||||
if (name.endsWith(CLST_EXTENSION)) {
|
||||
try (InputStream inputStream = new FileInputStream(input)) {
|
||||
load(inputStream);
|
||||
}
|
||||
} else if (name.endsWith(".jar")) {
|
||||
ZipSecurity.readZipEntries(input, (entry, in) -> {
|
||||
if (entry.getName().endsWith(CLST_EXTENSION)) {
|
||||
try {
|
||||
load(in);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to load jadx class set");
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
private void load(InputStream input) throws IOException, DecodeException {
|
||||
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
|
||||
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||
int readHeaderLength = in.read(header);
|
||||
int version = in.readByte();
|
||||
@@ -198,33 +354,142 @@ public class ClsSet {
|
||||
|| version != VERSION) {
|
||||
throw new DecodeException("Wrong jadx class set header");
|
||||
}
|
||||
int count = in.readInt();
|
||||
classes = new NClass[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
int clsCount = in.readInt();
|
||||
classes = new ClspClass[clsCount];
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
String name = readString(in);
|
||||
classes[i] = new NClass(name, i);
|
||||
classes[i] = new ClspClass(ArgType.object(name), i);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
int pCount = in.readByte();
|
||||
NClass[] parents = new NClass[pCount];
|
||||
for (int j = 0; j < pCount; j++) {
|
||||
parents[j] = classes[in.readInt()];
|
||||
}
|
||||
classes[i].setParents(parents);
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
ClspClass nClass = classes[i];
|
||||
ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
|
||||
nClass.setParents(readArgTypesArray(in));
|
||||
nClass.setTypeParameters(readArgTypesList(in));
|
||||
nClass.setMethods(readClsMethods(in, clsInfo));
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeString(DataOutputStream out, String name) throws IOException {
|
||||
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||
int mCount = in.readShort();
|
||||
List<ClspMethod> methods = new ArrayList<>(mCount);
|
||||
for (int j = 0; j < mCount; j++) {
|
||||
methods.add(readMethod(in, clsInfo));
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private ClspMethod readMethod(DataInputStream in, ClassInfo clsInfo) throws IOException {
|
||||
String name = readString(in);
|
||||
List<ArgType> argTypes = readArgTypesList(in);
|
||||
ArgType retType = readArgType(in);
|
||||
List<ArgType> genericArgTypes = readArgTypesList(in);
|
||||
if (genericArgTypes.isEmpty() || Objects.equals(genericArgTypes, argTypes)) {
|
||||
genericArgTypes = argTypes;
|
||||
}
|
||||
ArgType genericRetType = readArgType(in);
|
||||
if (Objects.equals(genericRetType, retType)) {
|
||||
genericRetType = retType;
|
||||
}
|
||||
List<ArgType> typeParameters = readArgTypesList(in);
|
||||
boolean varArgs = in.readBoolean();
|
||||
List<ArgType> throwList = readArgTypesList(in);
|
||||
MethodInfo methodInfo = MethodInfo.fromDetails(root, clsInfo, name, argTypes, retType);
|
||||
return new ClspMethod(methodInfo,
|
||||
genericArgTypes, genericRetType,
|
||||
typeParameters, varArgs, throwList);
|
||||
}
|
||||
|
||||
private List<ArgType> readArgTypesList(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> list = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
list.add(readArgType(in));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
|
||||
int count = in.readByte();
|
||||
if (count == -1) {
|
||||
return null;
|
||||
}
|
||||
if (count == 0) {
|
||||
return EMPTY_ARGTYPE_ARRAY;
|
||||
}
|
||||
ArgType[] arr = new ArgType[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
arr[i] = readArgType(in);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private ArgType readArgType(DataInputStream in) throws IOException {
|
||||
int ordinal = in.readByte();
|
||||
if (ordinal == -1) {
|
||||
return null;
|
||||
}
|
||||
if (ordinal >= TypeEnum.values().length) {
|
||||
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
|
||||
}
|
||||
switch (TypeEnum.values()[ordinal]) {
|
||||
case WILDCARD:
|
||||
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
|
||||
if (bound == ArgType.WildcardBound.UNBOUND) {
|
||||
return ArgType.WILDCARD;
|
||||
}
|
||||
ArgType objType = readArgType(in);
|
||||
return ArgType.wildcard(objType, bound);
|
||||
|
||||
case OUTER_GENERIC:
|
||||
ArgType outerType = readArgType(in);
|
||||
ArgType innerType = readArgType(in);
|
||||
return ArgType.outerGeneric(outerType, innerType);
|
||||
|
||||
case GENERIC:
|
||||
ArgType clsType = classes[in.readInt()].getClsType();
|
||||
return ArgType.generic(clsType, readArgTypesList(in));
|
||||
|
||||
case GENERIC_TYPE_VARIABLE:
|
||||
String typeVar = readString(in);
|
||||
List<ArgType> extendTypes = readArgTypesList(in);
|
||||
return ArgType.genericType(typeVar, extendTypes);
|
||||
|
||||
case OBJECT:
|
||||
return classes[in.readInt()].getClsType();
|
||||
|
||||
case ARRAY:
|
||||
return ArgType.array(readArgType(in));
|
||||
|
||||
case PRIMITIVE:
|
||||
char shortName = (char) in.readByte();
|
||||
return ArgType.parse(shortName);
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeByte(bytes.length);
|
||||
int len = bytes.length;
|
||||
if (len >= 0xFF) {
|
||||
throw new JadxRuntimeException("String is too long: " + name);
|
||||
}
|
||||
writeUnsignedByte(out, bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static String readString(DataInputStream in) throws IOException {
|
||||
int len = in.readByte();
|
||||
int len = readUnsignedByte(in);
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
private static String readString(DataInputStream in, int len) throws IOException {
|
||||
byte[] bytes = new byte[len];
|
||||
int count = in.read(bytes);
|
||||
while (count != len) {
|
||||
@@ -238,12 +503,23 @@ public class ClsSet {
|
||||
return new String(bytes, STRING_CHARSET);
|
||||
}
|
||||
|
||||
private static void writeUnsignedByte(DataOutputStream out, int value) throws IOException {
|
||||
if (value < 0 || value >= 0xFF) {
|
||||
throw new JadxRuntimeException("Unsigned byte value is too big: " + value);
|
||||
}
|
||||
out.writeByte(value);
|
||||
}
|
||||
|
||||
private static int readUnsignedByte(DataInputStream in) throws IOException {
|
||||
return ((int) in.readByte()) & 0xFF;
|
||||
}
|
||||
|
||||
public int getClassesCount() {
|
||||
return classes.length;
|
||||
}
|
||||
|
||||
public void addToMap(Map<String, NClass> nameMap) {
|
||||
for (NClass cls : classes) {
|
||||
public void addToMap(Map<String, ClspClass> nameMap) {
|
||||
for (ClspClass cls : classes) {
|
||||
nameMap.put(cls.getName(), cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
/**
|
||||
* Class node in classpath graph
|
||||
*/
|
||||
public class ClspClass {
|
||||
|
||||
private final ArgType clsType;
|
||||
private final int id;
|
||||
private ArgType[] parents;
|
||||
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
|
||||
private List<ArgType> typeParameters = Collections.emptyList();
|
||||
|
||||
public ClspClass(ArgType clsType, int id) {
|
||||
this.clsType = clsType;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return clsType.getObject();
|
||||
}
|
||||
|
||||
public ArgType getClsType() {
|
||||
return clsType;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ArgType[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
public void setParents(ArgType[] parents) {
|
||||
this.parents = parents;
|
||||
}
|
||||
|
||||
public Map<String, ClspMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
public List<ClspMethod> getSortedMethodsList() {
|
||||
List<ClspMethod> list = new ArrayList<>(methodsMap.size());
|
||||
list.addAll(methodsMap.values());
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setMethodsMap(Map<String, ClspMethod> methodsMap) {
|
||||
this.methodsMap = Objects.requireNonNull(methodsMap);
|
||||
}
|
||||
|
||||
public void setMethods(List<ClspMethod> methods) {
|
||||
Map<String, ClspMethod> map = new HashMap<>(methods.size());
|
||||
for (ClspMethod mth : methods) {
|
||||
map.put(mth.getMethodInfo().getShortId(), mth);
|
||||
}
|
||||
setMethodsMap(map);
|
||||
}
|
||||
|
||||
public List<ArgType> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
public void setTypeParameters(List<ArgType> typeParameters) {
|
||||
this.typeParameters = typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clsType.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ClspClass nClass = (ClspClass) o;
|
||||
return clsType.equals(nClass.clsType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return clsType.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -13,27 +10,44 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
* Classes hierarchy graph
|
||||
* Classes hierarchy graph with methods additional info
|
||||
*/
|
||||
public class ClspGraph {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||
|
||||
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
|
||||
private Map<String, NClass> nameMap;
|
||||
private final RootNode root;
|
||||
private final Map<String, Set<String>> superTypesCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private Map<String, ClspClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<>();
|
||||
|
||||
public ClspGraph(RootNode rootNode) {
|
||||
this.root = rootNode;
|
||||
}
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
ClsSet set = new ClsSet();
|
||||
set.load();
|
||||
ClsSet set = new ClsSet(root);
|
||||
set.loadFromClstFile();
|
||||
addClasspath(set);
|
||||
}
|
||||
|
||||
public void addClasspath(ClsSet set) {
|
||||
if (nameMap == null) {
|
||||
nameMap = new HashMap<String, NClass>(set.getClassesCount());
|
||||
nameMap = new HashMap<>(set.getClassesCount());
|
||||
set.addToMap(nameMap);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Classpath already loaded");
|
||||
@@ -44,82 +58,170 @@ public class ClspGraph {
|
||||
if (nameMap == null) {
|
||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||
}
|
||||
int size = classes.size();
|
||||
NClass[] nClasses = new NClass[size];
|
||||
int k = 0;
|
||||
for (ClassNode cls : classes) {
|
||||
nClasses[k++] = addClass(cls);
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
||||
addClass(cls);
|
||||
}
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
String rawName = cls.getRawName();
|
||||
NClass nClass = new NClass(rawName, -1);
|
||||
nameMap.put(rawName, nClass);
|
||||
return nClass;
|
||||
public boolean isClsKnown(String fullName) {
|
||||
return nameMap.containsKey(fullName);
|
||||
}
|
||||
|
||||
public ClspClass getClsDetails(ArgType type) {
|
||||
return nameMap.get(type.getObject());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IMethodDetails getMethodDetails(MethodInfo methodInfo) {
|
||||
ClspClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
ClspMethod clspMethod = getMethodFromClass(cls, methodInfo);
|
||||
if (clspMethod != null) {
|
||||
return clspMethod;
|
||||
}
|
||||
// deep search
|
||||
for (ArgType parent : cls.getParents()) {
|
||||
ClspClass clspParent = getClspClass(parent);
|
||||
if (clspParent != null) {
|
||||
ClspMethod methodFromParent = getMethodFromClass(clspParent, methodInfo);
|
||||
if (methodFromParent != null) {
|
||||
return methodFromParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
// all other methods in known ClspClass are 'simple'
|
||||
return new SimpleMethodDetails(methodInfo);
|
||||
}
|
||||
|
||||
private ClspMethod getMethodFromClass(ClspClass cls, MethodInfo methodInfo) {
|
||||
return cls.getMethodsMap().get(methodInfo.getShortId());
|
||||
}
|
||||
|
||||
private void addClass(ClassNode cls) {
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String rawName = clsType.getObject();
|
||||
ClspClass clspClass = new ClspClass(clsType, -1);
|
||||
clspClass.setParents(ClsSet.makeParentsArray(cls));
|
||||
nameMap.put(rawName, clspClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code clsName} instanceof {@code implClsName}
|
||||
*/
|
||||
public boolean isImplements(String clsName, String implClsName) {
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
Set<String> anc = getSuperTypes(clsName);
|
||||
return anc.contains(implClsName);
|
||||
}
|
||||
|
||||
public List<String> getImplementations(String clsName) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String cls : nameMap.keySet()) {
|
||||
if (isImplements(cls, clsName)) {
|
||||
list.add(cls);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public String getCommonAncestor(String clsName, String implClsName) {
|
||||
if (clsName.equals(implClsName)) {
|
||||
return clsName;
|
||||
}
|
||||
NClass cls = nameMap.get(implClsName);
|
||||
ClspClass cls = nameMap.get(implClsName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Missing class: {}", implClsName);
|
||||
missingClasses.add(clsName);
|
||||
return null;
|
||||
}
|
||||
if (isImplements(clsName, implClsName)) {
|
||||
return implClsName;
|
||||
}
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
Set<String> anc = getSuperTypes(clsName);
|
||||
return searchCommonParent(anc, cls);
|
||||
}
|
||||
|
||||
private String searchCommonParent(Set<String> anc, NClass cls) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
String name = p.getName();
|
||||
private String searchCommonParent(Set<String> anc, ClspClass cls) {
|
||||
for (ArgType p : cls.getParents()) {
|
||||
String name = p.getObject();
|
||||
if (anc.contains(name)) {
|
||||
return name;
|
||||
}
|
||||
String r = searchCommonParent(anc, p);
|
||||
if (r != null) {
|
||||
return r;
|
||||
ClspClass nCls = getClspClass(p);
|
||||
if (nCls != null) {
|
||||
String r = searchCommonParent(anc, nCls);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Set<String> getAncestors(String clsName) {
|
||||
Set<String> result = ancestorCache.get(clsName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
public Set<String> getSuperTypes(String clsName) {
|
||||
Set<String> fromCache = superTypesCache.get(clsName);
|
||||
if (fromCache != null) {
|
||||
return fromCache;
|
||||
}
|
||||
NClass cls = nameMap.get(clsName);
|
||||
ClspClass cls = nameMap.get(clsName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Missing class: {}", clsName);
|
||||
missingClasses.add(clsName);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
result = new HashSet<String>();
|
||||
addAncestorsNames(cls, result);
|
||||
Set<String> result = new HashSet<>();
|
||||
addSuperTypes(cls, result);
|
||||
return putInSuperTypesCache(clsName, result);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
|
||||
if (result.isEmpty()) {
|
||||
result = Collections.emptySet();
|
||||
Set<String> empty = Collections.emptySet();
|
||||
superTypesCache.put(clsName, result);
|
||||
return empty;
|
||||
}
|
||||
ancestorCache.put(clsName, result);
|
||||
superTypesCache.put(clsName, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addAncestorsNames(NClass cls, Set<String> result) {
|
||||
result.add(cls.getName());
|
||||
for (NClass p : cls.getParents()) {
|
||||
addAncestorsNames(p, result);
|
||||
private void addSuperTypes(ClspClass cls, Set<String> result) {
|
||||
for (ArgType parentType : cls.getParents()) {
|
||||
if (parentType == null) {
|
||||
continue;
|
||||
}
|
||||
ClspClass parentCls = getClspClass(parentType);
|
||||
if (parentCls != null) {
|
||||
boolean isNew = result.add(parentCls.getName());
|
||||
if (isNew) {
|
||||
addSuperTypes(parentCls, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ClspClass getClspClass(ArgType clsType) {
|
||||
ClspClass clspClass = nameMap.get(clsType.getObject());
|
||||
if (clspClass == null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("External class not found: {}", clsType.getObject());
|
||||
}
|
||||
}
|
||||
return clspClass;
|
||||
}
|
||||
|
||||
public void printMissingClasses() {
|
||||
int count = missingClasses.size();
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
LOG.warn("Found {} references to unknown classes", count);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
List<String> clsNames = new ArrayList<>(missingClasses);
|
||||
Collections.sort(clsNames);
|
||||
for (String cls : clsNames) {
|
||||
LOG.debug(" {}", cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
* Method node in classpath graph.
|
||||
*/
|
||||
public class ClspMethod implements IMethodDetails, Comparable<ClspMethod> {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
private final List<ArgType> argTypes;
|
||||
private final ArgType returnType;
|
||||
private final List<ArgType> typeParameters;
|
||||
private final List<ArgType> throwList;
|
||||
private final boolean varArg;
|
||||
|
||||
public ClspMethod(MethodInfo methodInfo,
|
||||
List<ArgType> argTypes, ArgType returnType,
|
||||
List<ArgType> typeParameters,
|
||||
boolean varArgs, List<ArgType> throwList) {
|
||||
this.methodInfo = methodInfo;
|
||||
this.argTypes = argTypes;
|
||||
this.returnType = returnType;
|
||||
this.typeParameters = typeParameters;
|
||||
this.throwList = throwList;
|
||||
this.varArg = varArgs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getArgTypes() {
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
public boolean containsGenericArgs() {
|
||||
return !Objects.equals(argTypes, methodInfo.getArgumentsTypes());
|
||||
}
|
||||
|
||||
public int getArgsCount() {
|
||||
return argTypes.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getTypeParameters() {
|
||||
return typeParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getThrows() {
|
||||
return throwList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return varArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ClspMethod)) {
|
||||
return false;
|
||||
}
|
||||
ClspMethod other = (ClspMethod) o;
|
||||
return methodInfo.equals(other.methodInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return methodInfo.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ClspMethod other) {
|
||||
return this.methodInfo.compareTo(other.methodInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ClspMth{");
|
||||
if (Utils.notEmpty(getTypeParameters())) {
|
||||
sb.append('<');
|
||||
sb.append(Utils.listToString(getTypeParameters()));
|
||||
sb.append("> ");
|
||||
}
|
||||
sb.append(getMethodInfo().getFullName());
|
||||
sb.append('(');
|
||||
sb.append(Utils.listToString(getArgTypes()));
|
||||
sb.append("):");
|
||||
sb.append(getReturnType());
|
||||
if (isVarArg()) {
|
||||
sb.append(" VARARG");
|
||||
}
|
||||
List<ArgType> throwsList = getThrows();
|
||||
if (Utils.notEmpty(throwsList)) {
|
||||
sb.append(" throws ").append(Utils.listToString(throwsList));
|
||||
}
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.api.DefaultJadxArgs;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Utility class for convert dex or jar to jadx classes set (.jcst)
|
||||
*/
|
||||
public class ConvertToClsSet {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
|
||||
|
||||
public static void usage() {
|
||||
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException, DecodeException {
|
||||
if (args.length < 2) {
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
File output = new File(args[0]);
|
||||
|
||||
List<InputFile> inputFiles = new ArrayList<InputFile>(args.length - 1);
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
File f = new File(args[i]);
|
||||
if (f.isDirectory()) {
|
||||
addFilesFromDirectory(f, inputFiles);
|
||||
} else {
|
||||
inputFiles.add(new InputFile(f));
|
||||
}
|
||||
}
|
||||
for (InputFile inputFile : inputFiles) {
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode(new DefaultJadxArgs());
|
||||
root.load(inputFiles);
|
||||
|
||||
ClsSet set = new ClsSet();
|
||||
set.load(root);
|
||||
set.save(output);
|
||||
LOG.info("Output: {}", output);
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
private static void addFilesFromDirectory(File dir,
|
||||
List<InputFile> inputFiles) throws IOException, DecodeException {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
addFilesFromDirectory(file, inputFiles);
|
||||
}
|
||||
String fileName = file.getName();
|
||||
if (fileName.endsWith(".dex")
|
||||
|| fileName.endsWith(".jar")
|
||||
|| fileName.endsWith(".apk")) {
|
||||
inputFiles.add(new InputFile(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
/**
|
||||
* Class node in classpath graph
|
||||
*/
|
||||
public class NClass {
|
||||
|
||||
private final String name;
|
||||
private NClass[] parents;
|
||||
private int id;
|
||||
|
||||
public NClass(String name, int id) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public NClass[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
|
||||
public void setParents(NClass[] parents) {
|
||||
this.parents = parents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
NClass nClass = (NClass) o;
|
||||
return name.equals(nClass.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
|
||||
public class SimpleMethodDetails implements IMethodDetails {
|
||||
|
||||
private final MethodInfo methodInfo;
|
||||
|
||||
public SimpleMethodDetails(MethodInfo methodInfo) {
|
||||
this.methodInfo = methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodInfo getMethodInfo() {
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getReturnType() {
|
||||
return methodInfo.getReturnType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getArgTypes() {
|
||||
return methodInfo.getArgumentsTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getTypeParameters() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getThrows() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVarArg() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleMethodDetails{" + methodInfo + '}';
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.data.IFieldData;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -11,14 +20,10 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class AnnotationGen {
|
||||
|
||||
private final ClassNode cls;
|
||||
@@ -50,7 +55,7 @@ public class AnnotationGen {
|
||||
if (aList == null || aList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
for (IAnnotation a : aList.getAll()) {
|
||||
formatAnnotation(code, a);
|
||||
code.add(' ');
|
||||
}
|
||||
@@ -61,50 +66,61 @@ public class AnnotationGen {
|
||||
if (aList == null || aList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
for (IAnnotation a : aList.getAll()) {
|
||||
String aCls = a.getAnnotationClass();
|
||||
if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
||||
// skip
|
||||
if (Consts.DEBUG) {
|
||||
code.startLine("// " + a);
|
||||
}
|
||||
} else {
|
||||
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
||||
code.startLine();
|
||||
formatAnnotation(code, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void formatAnnotation(CodeWriter code, Annotation a) {
|
||||
private void formatAnnotation(CodeWriter code, IAnnotation a) {
|
||||
code.add('@');
|
||||
classGen.useType(code, a.getType());
|
||||
Map<String, Object> vl = a.getValues();
|
||||
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
|
||||
if (annCls != null) {
|
||||
classGen.useClass(code, annCls);
|
||||
} else {
|
||||
classGen.useClass(code, a.getAnnotationClass());
|
||||
}
|
||||
|
||||
Map<String, EncodedValue> vl = a.getValues();
|
||||
if (!vl.isEmpty()) {
|
||||
code.add('(');
|
||||
if (vl.size() == 1 && vl.containsKey("value")) {
|
||||
encodeValue(code, vl.get("value"));
|
||||
} else {
|
||||
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
|
||||
Entry<String, Object> e = it.next();
|
||||
code.add(e.getKey());
|
||||
for (Iterator<Entry<String, EncodedValue>> it = vl.entrySet().iterator(); it.hasNext();) {
|
||||
Entry<String, EncodedValue> e = it.next();
|
||||
String paramName = getParamName(annCls, e.getKey());
|
||||
if (paramName.equals("value") && vl.size() == 1) {
|
||||
// don't add "value = " if no other parameters
|
||||
} else {
|
||||
code.add(paramName);
|
||||
code.add(" = ");
|
||||
encodeValue(code, e.getValue());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
encodeValue(cls.root(), code, e.getValue());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private String getParamName(@Nullable ClassNode annCls, String paramName) {
|
||||
if (annCls != null) {
|
||||
// TODO: save value type and search using signature
|
||||
MethodNode mth = annCls.searchMethodByShortName(paramName);
|
||||
if (mth != null) {
|
||||
return mth.getAlias();
|
||||
}
|
||||
}
|
||||
return paramName;
|
||||
}
|
||||
|
||||
public void addThrows(MethodNode mth, CodeWriter code) {
|
||||
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
|
||||
if (an != null) {
|
||||
Object exs = an.getDefaultValue();
|
||||
List<ArgType> throwList = mth.getThrows();
|
||||
if (!throwList.isEmpty()) {
|
||||
code.add(" throws ");
|
||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
|
||||
for (Iterator<ArgType> it = throwList.iterator(); it.hasNext();) {
|
||||
ArgType ex = it.next();
|
||||
classGen.useType(code, ex);
|
||||
if (it.hasNext()) {
|
||||
@@ -114,62 +130,97 @@ public class AnnotationGen {
|
||||
}
|
||||
}
|
||||
|
||||
public Object getAnnotationDefaultValue(String name) {
|
||||
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
||||
public EncodedValue getAnnotationDefaultValue(String name) {
|
||||
IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
||||
if (an != null) {
|
||||
Annotation defAnnotation = (Annotation) an.getDefaultValue();
|
||||
return defAnnotation.getValues().get(name);
|
||||
EncodedValue defValue = an.getDefaultValue();
|
||||
if (defValue != null) {
|
||||
IAnnotation defAnnotation = (IAnnotation) defValue.getValue();
|
||||
return defAnnotation.getValues().get(name);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: refactor this boilerplate code
|
||||
public void encodeValue(CodeWriter code, Object val) {
|
||||
if (val == null) {
|
||||
public void encodeValue(RootNode root, CodeWriter code, EncodedValue encodedValue) {
|
||||
if (encodedValue == null) {
|
||||
code.add("null");
|
||||
return;
|
||||
}
|
||||
if (val instanceof String) {
|
||||
code.add(StringUtils.unescapeString((String) val));
|
||||
} else if (val instanceof Integer) {
|
||||
code.add(TypeGen.formatInteger((Integer) val));
|
||||
} else if (val instanceof Character) {
|
||||
code.add(StringUtils.unescapeChar((Character) val));
|
||||
} else if (val instanceof Boolean) {
|
||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
||||
} else if (val instanceof Float) {
|
||||
code.add(TypeGen.formatFloat((Float) val));
|
||||
} else if (val instanceof Double) {
|
||||
code.add(TypeGen.formatDouble((Double) val));
|
||||
} else if (val instanceof Long) {
|
||||
code.add(TypeGen.formatLong((Long) val));
|
||||
} else if (val instanceof Short) {
|
||||
code.add(TypeGen.formatShort((Short) val));
|
||||
} else if (val instanceof Byte) {
|
||||
code.add(TypeGen.formatByte((Byte) val));
|
||||
} else if (val instanceof ArgType) {
|
||||
classGen.useType(code, (ArgType) val);
|
||||
code.add(".class");
|
||||
} else if (val instanceof FieldInfo) {
|
||||
// must be a static field
|
||||
FieldInfo field = (FieldInfo) val;
|
||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||
} else if (val instanceof Iterable) {
|
||||
code.add('{');
|
||||
Iterator<?> it = ((Iterable) val).iterator();
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
encodeValue(code, obj);
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
Object value = encodedValue.getValue();
|
||||
switch (encodedValue.getType()) {
|
||||
case ENCODED_NULL:
|
||||
code.add("null");
|
||||
break;
|
||||
case ENCODED_BOOLEAN:
|
||||
code.add(Boolean.TRUE.equals(value) ? "true" : "false");
|
||||
break;
|
||||
case ENCODED_BYTE:
|
||||
code.add(TypeGen.formatByte((Byte) value, false));
|
||||
break;
|
||||
case ENCODED_SHORT:
|
||||
code.add(TypeGen.formatShort((Short) value, false));
|
||||
break;
|
||||
case ENCODED_CHAR:
|
||||
code.add(getStringUtils().unescapeChar((Character) value));
|
||||
break;
|
||||
case ENCODED_INT:
|
||||
code.add(TypeGen.formatInteger((Integer) value, false));
|
||||
break;
|
||||
case ENCODED_LONG:
|
||||
code.add(TypeGen.formatLong((Long) value, false));
|
||||
break;
|
||||
case ENCODED_FLOAT:
|
||||
code.add(TypeGen.formatFloat((Float) value));
|
||||
break;
|
||||
case ENCODED_DOUBLE:
|
||||
code.add(TypeGen.formatDouble((Double) value));
|
||||
break;
|
||||
case ENCODED_STRING:
|
||||
code.add(getStringUtils().unescapeString((String) value));
|
||||
break;
|
||||
case ENCODED_TYPE:
|
||||
classGen.useType(code, ArgType.parse((String) value));
|
||||
code.add(".class");
|
||||
break;
|
||||
case ENCODED_ENUM:
|
||||
case ENCODED_FIELD:
|
||||
// must be a static field
|
||||
if (value instanceof IFieldData) {
|
||||
FieldInfo field = FieldInfo.fromData(root, (IFieldData) value);
|
||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||
} else if (value instanceof FieldInfo) {
|
||||
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected field type class: " + value.getClass());
|
||||
}
|
||||
}
|
||||
code.add('}');
|
||||
} else if (val instanceof Annotation) {
|
||||
formatAnnotation(code, (Annotation) val);
|
||||
} else {
|
||||
// TODO: also can be method values
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
||||
break;
|
||||
case ENCODED_METHOD:
|
||||
// TODO
|
||||
break;
|
||||
case ENCODED_ARRAY:
|
||||
code.add('{');
|
||||
Iterator<?> it = ((Iterable<?>) value).iterator();
|
||||
while (it.hasNext()) {
|
||||
EncodedValue v = (EncodedValue) it.next();
|
||||
encodeValue(cls.root(), code, v);
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add('}');
|
||||
break;
|
||||
case ENCODED_ANNOTATION:
|
||||
formatAnnotation(code, (IAnnotation) value);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Can't decode value: " + encodedValue.getType() + " (" + encodedValue + ')');
|
||||
}
|
||||
}
|
||||
|
||||
private StringUtils getStringUtils() {
|
||||
return cls.root().getStringUtils();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +1,77 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedType;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.FieldInitAttr;
|
||||
import jadx.core.dex.attributes.FieldInitAttr.InitType;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class ClassGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
|
||||
|
||||
public static final Comparator<MethodNode> METHOD_LINE_COMPARATOR = new Comparator<MethodNode>() {
|
||||
@Override
|
||||
public int compare(MethodNode a, MethodNode b) {
|
||||
return Utils.compare(a.getSourceLine(), b.getSourceLine());
|
||||
}
|
||||
};
|
||||
|
||||
private final ClassNode cls;
|
||||
private final ClassGen parentGen;
|
||||
private final AnnotationGen annotationGen;
|
||||
private final boolean fallback;
|
||||
private final boolean useImports;
|
||||
private final boolean showInconsistentCode;
|
||||
|
||||
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
|
||||
private final Set<ClassInfo> imports = new HashSet<>();
|
||||
private int clsDeclLine;
|
||||
|
||||
public ClassGen(ClassNode cls, IJadxArgs jadxArgs) {
|
||||
this(cls, null, jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||
private boolean bodyGenStarted;
|
||||
|
||||
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
|
||||
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||
}
|
||||
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen) {
|
||||
this(cls, parentClsGen, parentClsGen.fallback, parentClsGen.showInconsistentCode);
|
||||
this(cls, parentClsGen, parentClsGen.useImports, parentClsGen.fallback, parentClsGen.showInconsistentCode);
|
||||
}
|
||||
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback, boolean showBadCode) {
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean useImports, boolean fallback, boolean showBadCode) {
|
||||
this.cls = cls;
|
||||
this.parentGen = parentClsGen;
|
||||
this.fallback = fallback;
|
||||
this.useImports = useImports;
|
||||
this.showInconsistentCode = showBadCode;
|
||||
|
||||
this.annotationGen = new AnnotationGen(cls, this);
|
||||
@@ -76,7 +81,7 @@ public class ClassGen {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public CodeWriter makeClass() throws CodegenException {
|
||||
public ICodeInfo makeClass() throws CodegenException {
|
||||
CodeWriter clsBody = new CodeWriter();
|
||||
addClassCode(clsBody);
|
||||
|
||||
@@ -87,31 +92,33 @@ public class ClassGen {
|
||||
}
|
||||
int importsCount = imports.size();
|
||||
if (importsCount != 0) {
|
||||
List<String> sortImports = new ArrayList<String>(importsCount);
|
||||
for (ClassInfo ic : imports) {
|
||||
sortImports.add(ic.getAlias().getFullName());
|
||||
}
|
||||
Collections.sort(sortImports);
|
||||
|
||||
for (String imp : sortImports) {
|
||||
clsCode.startLine("import ").add(imp).add(';');
|
||||
}
|
||||
List<ClassInfo> sortedImports = new ArrayList<>(imports);
|
||||
sortedImports.sort(Comparator.comparing(ClassInfo::getAliasFullName));
|
||||
sortedImports.forEach(classInfo -> {
|
||||
clsCode.startLine("import ");
|
||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
clsCode.attachAnnotation(classNode);
|
||||
}
|
||||
clsCode.add(classInfo.getAliasFullName());
|
||||
clsCode.add(';');
|
||||
});
|
||||
clsCode.newLine();
|
||||
|
||||
sortImports.clear();
|
||||
imports.clear();
|
||||
}
|
||||
clsCode.add(clsBody);
|
||||
return clsCode;
|
||||
return clsCode.finish();
|
||||
}
|
||||
|
||||
public void addClassCode(CodeWriter code) throws CodegenException {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
|
||||
code.startLine("// jadx: inconsistent code");
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
addClassUsageInfo(code, cls);
|
||||
}
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
insertDecompilationProblems(code, cls);
|
||||
addClassDeclaration(code);
|
||||
addClassBody(code);
|
||||
}
|
||||
@@ -119,23 +126,24 @@ public class ClassGen {
|
||||
public void addClassDeclaration(CodeWriter clsCode) {
|
||||
AccessInfo af = cls.getAccessFlags();
|
||||
if (af.isInterface()) {
|
||||
af = af.remove(AccessFlags.ACC_ABSTRACT)
|
||||
.remove(AccessFlags.ACC_STATIC);
|
||||
af = af.remove(AccessFlags.ABSTRACT)
|
||||
.remove(AccessFlags.STATIC);
|
||||
} else if (af.isEnum()) {
|
||||
af = af.remove(AccessFlags.ACC_FINAL)
|
||||
.remove(AccessFlags.ACC_ABSTRACT)
|
||||
.remove(AccessFlags.ACC_STATIC);
|
||||
af = af.remove(AccessFlags.FINAL)
|
||||
.remove(AccessFlags.ABSTRACT)
|
||||
.remove(AccessFlags.STATIC);
|
||||
}
|
||||
|
||||
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
||||
if (!cls.getAlias().isInner()) {
|
||||
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
|
||||
}
|
||||
|
||||
annotationGen.addForClass(clsCode);
|
||||
insertSourceFileInfo(clsCode, cls);
|
||||
insertRenameInfo(clsCode, cls);
|
||||
clsCode.startLine(af.makeString());
|
||||
CodeGenUtils.addSourceFileInfo(clsCode, cls);
|
||||
clsCode.startLineWithNum(cls.getSourceLine());
|
||||
clsCode.add(af.makeString());
|
||||
if (af.isInterface()) {
|
||||
if (af.isAnnotation()) {
|
||||
clsCode.add('@');
|
||||
@@ -146,15 +154,16 @@ public class ClassGen {
|
||||
} else {
|
||||
clsCode.add("class ");
|
||||
}
|
||||
clsCode.add(cls.getShortName());
|
||||
clsCode.attachDefinition(cls);
|
||||
clsCode.add(cls.getClassInfo().getAliasShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenericMap());
|
||||
addGenericTypeParameters(clsCode, cls.getGenericTypeParameters(), true);
|
||||
clsCode.add(' ');
|
||||
|
||||
ArgType sup = cls.getSuperClass();
|
||||
if (sup != null
|
||||
&& !sup.equals(ArgType.OBJECT)
|
||||
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
|
||||
&& !cls.isEnum()) {
|
||||
clsCode.add("extends ");
|
||||
useClass(clsCode, sup);
|
||||
clsCode.add(' ');
|
||||
@@ -166,7 +175,7 @@ public class ClassGen {
|
||||
} else {
|
||||
clsCode.add("implements ");
|
||||
}
|
||||
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext();) {
|
||||
ArgType interf = it.next();
|
||||
useClass(clsCode, interf);
|
||||
if (it.hasNext()) {
|
||||
@@ -177,34 +186,37 @@ public class ClassGen {
|
||||
clsCode.add(' ');
|
||||
}
|
||||
}
|
||||
clsCode.attachDefinition(cls);
|
||||
}
|
||||
|
||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
|
||||
if (gmap == null || gmap.isEmpty()) {
|
||||
public boolean addGenericTypeParameters(CodeWriter code, List<ArgType> generics, boolean classDeclaration) {
|
||||
if (generics == null || generics.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
code.add('<');
|
||||
int i = 0;
|
||||
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
|
||||
ArgType type = e.getKey();
|
||||
List<ArgType> list = e.getValue();
|
||||
for (ArgType genericInfo : generics) {
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
if (genericInfo.isGenericType()) {
|
||||
code.add(genericInfo.getObject());
|
||||
} else {
|
||||
useClass(code, type);
|
||||
useClass(code, genericInfo);
|
||||
}
|
||||
List<ArgType> list = genericInfo.getExtendTypes();
|
||||
if (list != null && !list.isEmpty()) {
|
||||
code.add(" extends ");
|
||||
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
|
||||
ArgType g = it.next();
|
||||
if (g.isGenericType()) {
|
||||
code.add(g.getObject());
|
||||
} else {
|
||||
useClass(code, g);
|
||||
if (classDeclaration
|
||||
&& !cls.getClassInfo().isInner()
|
||||
&& cls.root().getArgs().isUseImports()) {
|
||||
addImport(ClassInfo.fromType(cls.root(), g));
|
||||
}
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(" & ");
|
||||
@@ -218,26 +230,50 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
||||
addClassBody(clsCode, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param printClassName allows to print the original class name as comment (e.g. for inlined
|
||||
* classes)
|
||||
*/
|
||||
public void addClassBody(CodeWriter clsCode, boolean printClassName) throws CodegenException {
|
||||
clsCode.add('{');
|
||||
setBodyGenStarted(true);
|
||||
clsDeclLine = clsCode.getLine();
|
||||
clsCode.incIndent();
|
||||
if (printClassName) {
|
||||
clsCode.startLine();
|
||||
clsCode.add("/* class " + cls.getFullName() + " */");
|
||||
}
|
||||
addFields(clsCode);
|
||||
addInnerClasses(clsCode, cls);
|
||||
addMethods(clsCode);
|
||||
addInnerClsAndMethods(clsCode);
|
||||
clsCode.decIndent();
|
||||
clsCode.startLine('}');
|
||||
}
|
||||
|
||||
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
if (innerCls.contains(AFlag.DONT_GENERATE)
|
||||
|| innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||
continue;
|
||||
}
|
||||
private void addInnerClsAndMethods(CodeWriter clsCode) {
|
||||
Stream.of(cls.getInnerClasses(), cls.getMethods())
|
||||
.flatMap(Collection::stream)
|
||||
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
|
||||
.sorted(Comparator.comparingInt(LineAttrNode::getSourceLine))
|
||||
.forEach(node -> {
|
||||
if (node instanceof ClassNode) {
|
||||
addInnerClass(clsCode, (ClassNode) node);
|
||||
} else {
|
||||
addMethod(clsCode, (MethodNode) node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addInnerClass(CodeWriter code, ClassNode innerCls) {
|
||||
try {
|
||||
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
||||
code.newLine();
|
||||
inClGen.addClassCode(code);
|
||||
imports.addAll(inClGen.getImports());
|
||||
} catch (Exception e) {
|
||||
innerCls.addError("Inner class code generation error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,28 +286,24 @@ public class ClassGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addMethods(CodeWriter code) {
|
||||
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
|
||||
for (MethodNode mth : methods) {
|
||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
if (code.getLine() != clsDeclLine) {
|
||||
code.newLine();
|
||||
}
|
||||
try {
|
||||
addMethod(code, mth);
|
||||
} catch (Exception e) {
|
||||
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
|
||||
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
|
||||
}
|
||||
private void addMethod(CodeWriter code, MethodNode mth) {
|
||||
if (code.getLine() != clsDeclLine) {
|
||||
code.newLine();
|
||||
}
|
||||
int savedIndent = code.getIndent();
|
||||
try {
|
||||
addMethodCode(code, mth);
|
||||
} catch (Exception e) {
|
||||
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||
throw new JadxRuntimeException("Method generation error", e);
|
||||
}
|
||||
code.newLine().add("/*");
|
||||
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e));
|
||||
Utils.appendStackTrace(code, e);
|
||||
code.newLine().add("*/");
|
||||
code.setIndent(savedIndent);
|
||||
mth.addError("Method generation error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
|
||||
List<MethodNode> out = new ArrayList<MethodNode>(methods);
|
||||
Collections.sort(out, METHOD_LINE_COMPARATOR);
|
||||
return out;
|
||||
}
|
||||
|
||||
private boolean isMethodsPresents() {
|
||||
@@ -283,31 +315,21 @@ public class ClassGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
||||
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||
CodeGenUtils.addComments(code, mth);
|
||||
if (mth.isNoCode()) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
mthGen.addDefinition(code);
|
||||
if (cls.getAccessFlags().isAnnotation()) {
|
||||
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||
if (def != null) {
|
||||
code.add(" default ");
|
||||
annotationGen.encodeValue(code, def);
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
} else {
|
||||
insertDecompilationProblems(code, mth);
|
||||
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
||||
if (badCode) {
|
||||
code.startLine("/* JADX WARNING: inconsistent code. */");
|
||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||
ErrorsCounter.methodError(mth, "Inconsistent code");
|
||||
if (showInconsistentCode) {
|
||||
mth.remove(AFlag.INCONSISTENT_CODE);
|
||||
badCode = false;
|
||||
}
|
||||
if (badCode && showInconsistentCode) {
|
||||
mth.remove(AFlag.INCONSISTENT_CODE);
|
||||
badCode = false;
|
||||
}
|
||||
MethodGen mthGen;
|
||||
if (badCode || mth.contains(AType.JADX_ERROR) || fallback) {
|
||||
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
|
||||
mthGen = MethodGen.getFallbackMethodGen(mth);
|
||||
} else {
|
||||
mthGen = new MethodGen(this, mth);
|
||||
@@ -317,42 +339,77 @@ public class ClassGen {
|
||||
}
|
||||
code.add('{');
|
||||
code.incIndent();
|
||||
insertSourceFileInfo(code, mth);
|
||||
if (fallback) {
|
||||
mthGen.addFallbackMethodCode(code);
|
||||
} else {
|
||||
mthGen.addInstructions(code);
|
||||
}
|
||||
mthGen.addInstructions(code);
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
}
|
||||
}
|
||||
|
||||
public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
|
||||
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
|
||||
if (!errors.isEmpty()) {
|
||||
errors.stream().distinct().sorted().forEach(err -> {
|
||||
code.startLine("/* JADX ERROR: ").add(err.getError());
|
||||
Throwable cause = err.getCause();
|
||||
if (cause != null) {
|
||||
code.incIndent();
|
||||
Utils.appendStackTrace(code, cause);
|
||||
code.decIndent();
|
||||
}
|
||||
code.add("*/");
|
||||
});
|
||||
}
|
||||
List<String> warns = node.getAll(AType.JADX_WARN);
|
||||
if (!warns.isEmpty()) {
|
||||
warns.stream().distinct().sorted()
|
||||
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
|
||||
}
|
||||
}
|
||||
|
||||
private void addFields(CodeWriter code) throws CodegenException {
|
||||
addEnumFields(code);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
annotationGen.addForField(code, f);
|
||||
code.startLine(f.getAccessFlags().makeString());
|
||||
useType(code, f.getType());
|
||||
code.add(' ');
|
||||
code.add(f.getAlias());
|
||||
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
|
||||
if (fv != null) {
|
||||
code.add(" = ");
|
||||
if (fv.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType()));
|
||||
} else {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
code.attachDefinition(f);
|
||||
addField(code, f);
|
||||
}
|
||||
}
|
||||
|
||||
public void addField(CodeWriter code, FieldNode f) {
|
||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
addFieldUsageInfo(code, f);
|
||||
}
|
||||
CodeGenUtils.addComments(code, f);
|
||||
annotationGen.addForField(code, f);
|
||||
|
||||
if (f.getFieldInfo().isRenamed()) {
|
||||
code.newLine();
|
||||
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
||||
}
|
||||
code.startLine(f.getAccessFlags().makeString());
|
||||
useType(code, f.getType());
|
||||
code.add(' ');
|
||||
code.attachDefinition(f);
|
||||
code.add(f.getAlias());
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null) {
|
||||
code.add(" = ");
|
||||
if (fv.getValueType() == InitType.CONST) {
|
||||
EncodedValue encodedValue = fv.getEncodedValue();
|
||||
if (encodedValue.getType() == EncodedType.ENCODED_NULL) {
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
|
||||
} else {
|
||||
annotationGen.encodeValue(cls.root(), code, encodedValue);
|
||||
}
|
||||
} else if (fv.getValueType() == InitType.INSN) {
|
||||
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
|
||||
addInsnBody(insnGen, code, fv.getInsn());
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
}
|
||||
|
||||
private boolean isFieldsPresents() {
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (!field.contains(AFlag.DONT_GENERATE)) {
|
||||
@@ -368,17 +425,17 @@ public class ClassGen {
|
||||
return;
|
||||
}
|
||||
InsnGen igen = null;
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getField().getAlias());
|
||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||
if (constrInsn.getArgsCount() > f.getStartArg()) {
|
||||
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
|
||||
int skipCount = getEnumCtrSkipArgsCount(callMth);
|
||||
if (constrInsn.getArgsCount() > skipCount) {
|
||||
if (igen == null) {
|
||||
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
|
||||
igen = new InsnGen(mthGen, false);
|
||||
igen = makeInsnGen(enumFields.getStaticMethod());
|
||||
}
|
||||
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
|
||||
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
|
||||
igen.generateMethodArguments(code, constrInsn, 0, callMth);
|
||||
}
|
||||
if (f.getCls() != null) {
|
||||
code.add(' ');
|
||||
@@ -399,6 +456,29 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private int getEnumCtrSkipArgsCount(@Nullable MethodNode callMth) {
|
||||
if (callMth != null) {
|
||||
SkipMethodArgsAttr skipArgsAttr = callMth.get(AType.SKIP_MTH_ARGS);
|
||||
if (skipArgsAttr != null) {
|
||||
return skipArgsAttr.getSkipCount();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private InsnGen makeInsnGen(MethodNode mth) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
return new InsnGen(mthGen, false);
|
||||
}
|
||||
|
||||
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) {
|
||||
try {
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
|
||||
} catch (Exception e) {
|
||||
cls.addError("Failed to generate init code", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void useType(CodeWriter code, ArgType type) {
|
||||
PrimitiveType stype = type.getPrimitiveType();
|
||||
if (stype == null) {
|
||||
@@ -417,23 +497,35 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, String rawCls) {
|
||||
useClass(code, ArgType.object(rawCls));
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ArgType type) {
|
||||
useClass(code, ClassInfo.extCls(cls.dex(), type));
|
||||
ArgType[] generics = type.getGenericTypes();
|
||||
ArgType outerType = type.getOuterType();
|
||||
if (outerType != null) {
|
||||
useClass(code, outerType);
|
||||
code.add('.');
|
||||
// import not needed, force use short name
|
||||
useClassShortName(code, type.getObject());
|
||||
return;
|
||||
}
|
||||
|
||||
useClass(code, ClassInfo.fromType(cls.root(), type));
|
||||
List<ArgType> generics = type.getGenericTypes();
|
||||
if (generics != null) {
|
||||
code.add('<');
|
||||
int len = generics.length;
|
||||
int len = generics.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
ArgType gt = generics[i];
|
||||
ArgType gt = generics.get(i);
|
||||
ArgType wt = gt.getWildcardType();
|
||||
if (wt != null) {
|
||||
code.add('?');
|
||||
int bounds = gt.getWildcardBounds();
|
||||
if (bounds != 0) {
|
||||
code.add(bounds == -1 ? " super " : " extends ");
|
||||
ArgType.WildcardBound bound = gt.getWildcardBound();
|
||||
code.add(bound.getStr());
|
||||
if (bound != ArgType.WildcardBound.UNBOUND) {
|
||||
useType(code, wt);
|
||||
}
|
||||
} else {
|
||||
@@ -444,27 +536,49 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
private void useClassShortName(CodeWriter code, String object) {
|
||||
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
|
||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
code.attachAnnotation(classNode);
|
||||
}
|
||||
String baseClass = useClassInternal(cls.getAlias(), classInfo.getAlias());
|
||||
code.add(baseClass);
|
||||
code.add(classInfo.getAliasShortName());
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
useClass(code, classNode);
|
||||
} else {
|
||||
addClsName(code, classInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassNode classNode) {
|
||||
code.attachAnnotation(classNode);
|
||||
addClsName(code, classNode.getClassInfo());
|
||||
}
|
||||
|
||||
private void addClsName(CodeWriter code, ClassInfo classInfo) {
|
||||
String clsName = useClassInternal(cls.getClassInfo(), classInfo);
|
||||
code.add(clsName);
|
||||
}
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||
String fullName = extClsInfo.getFullName();
|
||||
if (fallback) {
|
||||
String fullName = extClsInfo.getAliasFullName();
|
||||
if (fallback || !useImports) {
|
||||
return fullName;
|
||||
}
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String shortName = extClsInfo.getAliasShortName();
|
||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||
return shortName;
|
||||
}
|
||||
if (isClassInnerFor(useCls, extClsInfo)) {
|
||||
return shortName;
|
||||
}
|
||||
if (extClsInfo.isInner()) {
|
||||
return expandInnerClassName(useCls, extClsInfo);
|
||||
}
|
||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||
return shortName;
|
||||
}
|
||||
@@ -472,23 +586,22 @@ public class ClassGen {
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||
return shortName;
|
||||
}
|
||||
// don't add import if class not public (must be accessed using inheritance)
|
||||
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
|
||||
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
||||
return shortName;
|
||||
}
|
||||
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
|
||||
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
|
||||
fullName = extClsInfo.getNameWithoutPackage();
|
||||
// ignore classes from default package
|
||||
if (extClsInfo.isDefaultPackage()) {
|
||||
return shortName;
|
||||
}
|
||||
if (extClsInfo.getAliasPkg().equals(useCls.getAliasPkg())) {
|
||||
fullName = extClsInfo.getAliasNameWithoutPackage();
|
||||
}
|
||||
for (ClassInfo importCls : getImports()) {
|
||||
if (!importCls.equals(extClsInfo)
|
||||
&& importCls.getShortName().equals(shortName)) {
|
||||
&& importCls.getAliasShortName().equals(shortName)) {
|
||||
if (extClsInfo.isInner()) {
|
||||
String parent = useClassInternal(useCls, extClsInfo.getParentClass().getAlias());
|
||||
return parent + "." + shortName;
|
||||
String parent = useClassInternal(useCls, extClsInfo.getParentClass());
|
||||
return parent + '.' + shortName;
|
||||
} else {
|
||||
return fullName;
|
||||
}
|
||||
@@ -498,15 +611,35 @@ public class ClassGen {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
private String expandInnerClassName(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||
List<ClassInfo> clsList = new ArrayList<>();
|
||||
clsList.add(extClsInfo);
|
||||
ClassInfo parentCls = extClsInfo.getParentClass();
|
||||
boolean addImport = true;
|
||||
while (parentCls != null) {
|
||||
if (parentCls == useCls || isClassInnerFor(useCls, parentCls)) {
|
||||
addImport = false;
|
||||
break;
|
||||
}
|
||||
clsList.add(parentCls);
|
||||
parentCls = parentCls.getParentClass();
|
||||
}
|
||||
Collections.reverse(clsList);
|
||||
if (addImport) {
|
||||
addImport(clsList.get(0));
|
||||
}
|
||||
return Utils.listToString(clsList, ".", ClassInfo::getAliasShortName);
|
||||
}
|
||||
|
||||
private void addImport(ClassInfo classInfo) {
|
||||
if (parentGen != null) {
|
||||
parentGen.addImport(classInfo.getAlias());
|
||||
parentGen.addImport(classInfo);
|
||||
} else {
|
||||
imports.add(classInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<ClassInfo> getImports() {
|
||||
public Set<ClassInfo> getImports() {
|
||||
if (parentGen != null) {
|
||||
return parentGen.getImports();
|
||||
} else {
|
||||
@@ -527,42 +660,69 @@ public class ClassGen {
|
||||
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
|
||||
if (inner.isInner()) {
|
||||
ClassInfo p = inner.getParentClass();
|
||||
return p.equals(parent) || isClassInnerFor(p, parent);
|
||||
return Objects.equals(p, parent) || isClassInnerFor(p, parent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
|
||||
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
|
||||
if (useCls == null) {
|
||||
return false;
|
||||
}
|
||||
String shortName = searchCls.getShortName();
|
||||
if (useCls.getShortName().equals(shortName)) {
|
||||
String shortName = searchCls.getAliasShortName();
|
||||
if (useCls.getAliasShortName().equals(shortName)) {
|
||||
return true;
|
||||
}
|
||||
ClassNode classNode = dex.resolveClass(useCls);
|
||||
ClassNode classNode = root.resolveClass(useCls);
|
||||
if (classNode != null) {
|
||||
for (ClassNode inner : classNode.getInnerClasses()) {
|
||||
if (inner.getShortName().equals(shortName)
|
||||
&& !inner.getAlias().equals(searchCls)) {
|
||||
&& !inner.getFullName().equals(searchCls.getAliasFullName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchCollision(dex, useCls.getParentClass(), searchCls);
|
||||
}
|
||||
|
||||
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
|
||||
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
|
||||
if (sourceFileAttr != null) {
|
||||
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
|
||||
}
|
||||
return searchCollision(root, useCls.getParentClass(), searchCls);
|
||||
}
|
||||
|
||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (classInfo.isRenamed()) {
|
||||
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
|
||||
if (classInfo.hasAlias()) {
|
||||
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
private static void addClassUsageInfo(CodeWriter code, ClassNode cls) {
|
||||
List<ClassNode> deps = cls.getDependencies();
|
||||
code.startLine("// deps - ").add(Integer.toString(deps.size()));
|
||||
for (ClassNode depCls : deps) {
|
||||
code.startLine("// ").add(depCls.getClassInfo().getFullName());
|
||||
}
|
||||
List<ClassNode> useIn = cls.getUseIn();
|
||||
code.startLine("// use in - ").add(Integer.toString(useIn.size()));
|
||||
for (ClassNode useCls : useIn) {
|
||||
code.startLine("// ").add(useCls.getClassInfo().getFullName());
|
||||
}
|
||||
List<MethodNode> useInMths = cls.getUseInMth();
|
||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||
for (MethodNode useMth : useInMths) {
|
||||
code.startLine("// ").add(useMth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static void addMthUsageInfo(CodeWriter code, MethodNode mth) {
|
||||
List<MethodNode> useInMths = mth.getUseIn();
|
||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||
for (MethodNode useMth : useInMths) {
|
||||
code.startLine("// ").add(useMth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFieldUsageInfo(CodeWriter code, FieldNode fieldNode) {
|
||||
List<MethodNode> useInMths = fieldNode.getUseIn();
|
||||
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
|
||||
for (MethodNode useMth : useInMths) {
|
||||
code.startLine("// ").add(useMth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,4 +737,12 @@ public class ClassGen {
|
||||
public boolean isFallbackMode() {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public boolean isBodyGenStarted() {
|
||||
return bodyGenStarted;
|
||||
}
|
||||
|
||||
public void setBodyGenStarted(boolean bodyGenStarted) {
|
||||
this.bodyGenStarted = bodyGenStarted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,62 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.core.codegen.json.JsonCodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class CodeGen extends AbstractVisitor {
|
||||
public class CodeGen {
|
||||
|
||||
private final IJadxArgs args;
|
||||
public static ICodeInfo generate(ClassNode cls) {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
return ICodeInfo.EMPTY;
|
||||
}
|
||||
JadxArgs args = cls.root().getArgs();
|
||||
switch (args.getOutputFormat()) {
|
||||
case JAVA:
|
||||
return generateJavaCode(cls, args);
|
||||
|
||||
public CodeGen(IJadxArgs args) {
|
||||
this.args = args;
|
||||
case JSON:
|
||||
return generateJson(cls);
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown output format");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws CodegenException {
|
||||
private static ICodeInfo generateJavaCode(ClassNode cls, JadxArgs args) {
|
||||
ClassGen clsGen = new ClassGen(cls, args);
|
||||
CodeWriter clsCode = clsGen.makeClass();
|
||||
clsCode.finish();
|
||||
cls.setCode(clsCode);
|
||||
return false;
|
||||
return wrapCodeGen(cls, clsGen::makeClass);
|
||||
}
|
||||
|
||||
private static ICodeInfo generateJson(ClassNode cls) {
|
||||
JsonCodeGen codeGen = new JsonCodeGen(cls);
|
||||
String clsJson = wrapCodeGen(cls, codeGen::process);
|
||||
return new SimpleCodeInfo(clsJson);
|
||||
}
|
||||
|
||||
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
|
||||
try {
|
||||
return codeGenFunc.call();
|
||||
} catch (Exception e) {
|
||||
if (cls.contains(AFlag.RESTART_CODEGEN)) {
|
||||
cls.remove(AFlag.RESTART_CODEGEN);
|
||||
try {
|
||||
return codeGenFunc.call();
|
||||
} catch (Exception ex) {
|
||||
throw new JadxRuntimeException("Code generation error after restart", ex);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Code generation error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CodeGen() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.impl.SimpleCodeInfo;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class CodeWriter {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
|
||||
private static final int MAX_FILENAME_LENGTH = 128;
|
||||
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
public static final String INDENT = " ";
|
||||
public static final String INDENT_STR = " ";
|
||||
|
||||
private static final boolean ADD_LINE_NUMBERS = false;
|
||||
|
||||
private static final String[] INDENT_CACHE = {
|
||||
"",
|
||||
INDENT,
|
||||
INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT + INDENT + INDENT,
|
||||
INDENT_STR,
|
||||
INDENT_STR + INDENT_STR,
|
||||
INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
};
|
||||
|
||||
private final StringBuilder buf = new StringBuilder();
|
||||
private StringBuilder buf;
|
||||
@Nullable
|
||||
private String code;
|
||||
private String indentStr;
|
||||
private int indent;
|
||||
|
||||
@@ -43,10 +45,12 @@ public class CodeWriter {
|
||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||
|
||||
public CodeWriter() {
|
||||
this.buf = new StringBuilder();
|
||||
this.indent = 0;
|
||||
this.indentStr = "";
|
||||
if (ADD_LINE_NUMBERS) {
|
||||
incIndent(2);
|
||||
incIndent(3);
|
||||
add(indentStr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +94,17 @@ public class CodeWriter {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter addMultiLine(String str) {
|
||||
if (str.contains(NL)) {
|
||||
buf.append(str.replace(NL, NL + indentStr));
|
||||
line += StringUtils.countMatches(str, NL);
|
||||
offset = 0;
|
||||
} else {
|
||||
buf.append(str);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(String str) {
|
||||
buf.append(str);
|
||||
offset += str.length();
|
||||
@@ -113,7 +128,7 @@ public class CodeWriter {
|
||||
}
|
||||
line += code.line;
|
||||
offset = code.offset;
|
||||
buf.append(code);
|
||||
buf.append(code.buf);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -123,7 +138,7 @@ public class CodeWriter {
|
||||
}
|
||||
|
||||
public CodeWriter addIndent() {
|
||||
add(INDENT);
|
||||
add(INDENT_STR);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -144,11 +159,7 @@ public class CodeWriter {
|
||||
if (curIndent < INDENT_CACHE.length) {
|
||||
this.indentStr = INDENT_CACHE[curIndent];
|
||||
} else {
|
||||
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
|
||||
for (int i = 0; i < curIndent; i++) {
|
||||
s.append(INDENT);
|
||||
}
|
||||
this.indentStr = s.toString();
|
||||
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +189,11 @@ public class CodeWriter {
|
||||
return indent;
|
||||
}
|
||||
|
||||
public void setIndent(int indent) {
|
||||
this.indent = indent;
|
||||
updateIndent();
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
@@ -194,25 +210,26 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public Object attachDefinition(LineAttrNode obj) {
|
||||
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
public void attachDefinition(LineAttrNode obj) {
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
}
|
||||
|
||||
public Object attachAnnotation(Object obj) {
|
||||
return attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
public void attachAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
}
|
||||
|
||||
public void attachLineAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, 0));
|
||||
}
|
||||
|
||||
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||
if (annotations.isEmpty()) {
|
||||
annotations = new HashMap<CodePosition, Object>();
|
||||
annotations = new HashMap<>();
|
||||
}
|
||||
return annotations.put(pos, obj);
|
||||
}
|
||||
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void attachSourceLine(int sourceLine) {
|
||||
if (sourceLine == 0) {
|
||||
return;
|
||||
@@ -222,104 +239,53 @@ public class CodeWriter {
|
||||
|
||||
private void attachSourceLine(int decompiledLine, int sourceLine) {
|
||||
if (lineMap.isEmpty()) {
|
||||
lineMap = new TreeMap<Integer, Integer>();
|
||||
lineMap = new TreeMap<>();
|
||||
}
|
||||
lineMap.put(decompiledLine, sourceLine);
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lineMap;
|
||||
public ICodeInfo finish() {
|
||||
removeFirstEmptyLine();
|
||||
processDefinitionAnnotations();
|
||||
code = buf.toString();
|
||||
buf = null;
|
||||
return new SimpleCodeInfo(code, lineMap, annotations);
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
buf.trimToSize();
|
||||
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<CodePosition, Object> entry = it.next();
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||
l.setDecompiledLine(entry.getKey().getLine());
|
||||
it.remove();
|
||||
}
|
||||
private void removeFirstEmptyLine() {
|
||||
int len = NL.length();
|
||||
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
||||
buf.delete(0, len);
|
||||
}
|
||||
}
|
||||
|
||||
private static String removeFirstEmptyLine(String str) {
|
||||
if (str.startsWith(NL)) {
|
||||
return str.substring(NL.length());
|
||||
private void processDefinitionAnnotations() {
|
||||
if (!annotations.isEmpty()) {
|
||||
annotations.entrySet().removeIf(entry -> {
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||
l.setDecompiledLine(entry.getKey().getLine());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
public int bufLength() {
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return buf.length() == 0;
|
||||
}
|
||||
|
||||
public boolean notEmpty() {
|
||||
return buf.length() != 0;
|
||||
public String getCodeStr() {
|
||||
if (code == null) {
|
||||
finish();
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public void save(File dir, String subDir, String fileName) {
|
||||
save(dir, new File(subDir, fileName).getPath());
|
||||
}
|
||||
|
||||
public void save(File dir, String fileName) {
|
||||
save(new File(dir, fileName));
|
||||
}
|
||||
|
||||
public void save(File file) {
|
||||
String name = file.getName();
|
||||
if (name.length() > MAX_FILENAME_LENGTH) {
|
||||
int dotIndex = name.indexOf('.');
|
||||
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
|
||||
if (cutAt <= 0) {
|
||||
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
|
||||
} else {
|
||||
name = name.substring(0, cutAt) + name.substring(dotIndex);
|
||||
}
|
||||
file = new File(file.getParentFile(), name);
|
||||
}
|
||||
|
||||
PrintWriter out = null;
|
||||
try {
|
||||
FileUtils.makeDirsForFile(file);
|
||||
out = new PrintWriter(file, "UTF-8");
|
||||
String code = buf.toString();
|
||||
code = removeFirstEmptyLine(code);
|
||||
out.println(code);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Save file error", e);
|
||||
} finally {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return buf.toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof CodeWriter)) {
|
||||
return false;
|
||||
}
|
||||
CodeWriter that = (CodeWriter) o;
|
||||
return buf.toString().equals(that.buf.toString());
|
||||
return code != null ? code : buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -11,22 +16,13 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.conditions.Compare;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ConditionGen extends InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
|
||||
|
||||
private static class CondStack {
|
||||
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
|
||||
private final Queue<IfCondition> stack = new LinkedList<>();
|
||||
|
||||
public Queue<IfCondition> getStack() {
|
||||
return stack;
|
||||
@@ -126,7 +122,7 @@ public class ConditionGen extends InsnGen {
|
||||
wrap(code, firstArg);
|
||||
return;
|
||||
}
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
|
||||
mth.addWarn("Unsupported boolean condition " + op.getSymbol());
|
||||
}
|
||||
|
||||
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
||||
@@ -159,7 +155,7 @@ public class ConditionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private boolean isWrapNeeded(IfCondition condition) {
|
||||
if (condition.isCompare()) {
|
||||
if (condition.isCompare() || condition.contains(AFlag.DONT_WRAP)) {
|
||||
return false;
|
||||
}
|
||||
return condition.getMode() != Mode.NOT;
|
||||
@@ -179,6 +175,9 @@ public class ConditionGen extends InsnGen {
|
||||
case DIV:
|
||||
case REM:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
switch (insnType) {
|
||||
@@ -189,10 +188,10 @@ public class ConditionGen extends InsnGen {
|
||||
case CONST:
|
||||
case ARRAY_LENGTH:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ArithOp;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.ConstClassNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.FillArrayNode;
|
||||
import jadx.core.dex.instructions.FillArrayInsn;
|
||||
import jadx.core.dex.instructions.FilledNewArrayNode;
|
||||
import jadx.core.dex.instructions.GotoNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
@@ -21,14 +33,15 @@ import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.NewArrayNode;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.SwitchInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.FieldArg;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.Named;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -37,22 +50,10 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import static jadx.core.utils.android.AndroidResourcesUtils.handleAppResField;
|
||||
|
||||
public class InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
|
||||
@@ -61,6 +62,7 @@ public class InsnGen {
|
||||
protected final MethodNode mth;
|
||||
protected final RootNode root;
|
||||
protected final boolean fallback;
|
||||
protected final boolean attachInsns;
|
||||
|
||||
protected enum Flags {
|
||||
BODY_ONLY,
|
||||
@@ -71,8 +73,9 @@ public class InsnGen {
|
||||
public InsnGen(MethodGen mgen, boolean fallback) {
|
||||
this.mgen = mgen;
|
||||
this.mth = mgen.getMethodNode();
|
||||
this.root = mth.dex().root();
|
||||
this.root = mth.root();
|
||||
this.fallback = fallback;
|
||||
this.attachInsns = root.getArgs().isJsonOutput();
|
||||
}
|
||||
|
||||
private boolean isFallback() {
|
||||
@@ -80,9 +83,9 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||
int len = code.length();
|
||||
int len = code.bufLength();
|
||||
addArg(code, arg, true);
|
||||
if (len != code.length()) {
|
||||
if (len != code.bufLength()) {
|
||||
code.add('.');
|
||||
}
|
||||
}
|
||||
@@ -97,22 +100,26 @@ public class InsnGen {
|
||||
} else if (arg.isLiteral()) {
|
||||
code.add(lit((LiteralArg) arg));
|
||||
} else if (arg.isInsnWrap()) {
|
||||
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
||||
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
|
||||
addWrappedArg(code, (InsnWrapArg) arg, wrap);
|
||||
} else if (arg.isNamed()) {
|
||||
code.add(((Named) arg).getName());
|
||||
} else if (arg.isField()) {
|
||||
FieldArg f = (FieldArg) arg;
|
||||
if (f.isStatic()) {
|
||||
staticField(code, f.getField());
|
||||
} else {
|
||||
instanceField(code, f.getField(), f.getInstanceArg());
|
||||
}
|
||||
} else {
|
||||
throw new CodegenException("Unknown arg type " + arg);
|
||||
}
|
||||
}
|
||||
|
||||
private void addWrappedArg(CodeWriter code, InsnWrapArg arg, boolean wrap) throws CodegenException {
|
||||
InsnNode wrapInsn = arg.getWrapInsn();
|
||||
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
||||
code.add('(');
|
||||
makeInsn(wrapInsn, code, Flags.INLINE);
|
||||
code.add(')');
|
||||
} else {
|
||||
Flags flags = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
||||
makeInsn(wrapInsn, code, flags);
|
||||
}
|
||||
}
|
||||
|
||||
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
|
||||
RegisterArg arg = insn.getResult();
|
||||
if (insn.contains(AFlag.DECLARE_VAR)) {
|
||||
@@ -123,63 +130,71 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
useType(code, arg.getType());
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignArg(arg));
|
||||
declareVar(code, arg.getSVar().getCodeVar());
|
||||
}
|
||||
|
||||
private static String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
|
||||
public void declareVar(CodeWriter code, CodeVar codeVar) {
|
||||
if (codeVar.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
useType(code, codeVar.getType());
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg, mth, fallback);
|
||||
}
|
||||
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
ClassNode pCls = mth.getParentClass();
|
||||
FieldNode fieldNode = pCls.searchField(field);
|
||||
while (fieldNode == null
|
||||
&& pCls.getParentClass() != pCls
|
||||
&& pCls.getParentClass() != null) {
|
||||
pCls = pCls.getParentClass();
|
||||
fieldNode = pCls.searchField(field);
|
||||
}
|
||||
FieldNode fieldNode = pCls.root().deepResolveField(field);
|
||||
if (fieldNode != null) {
|
||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||
if (replace != null) {
|
||||
FieldInfo info = replace.getFieldInfo();
|
||||
if (replace.isOuterClass()) {
|
||||
useClass(code, info.getDeclClass());
|
||||
code.add(".this");
|
||||
switch (replace.getReplaceType()) {
|
||||
case CLASS_INSTANCE:
|
||||
useClass(code, replace.getClsRef());
|
||||
code.add(".this");
|
||||
break;
|
||||
case VAR:
|
||||
addArg(code, replace.getVarRef());
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
addArgDot(code, arg);
|
||||
fieldNode = mth.dex().resolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
code.add(field.getAlias());
|
||||
if (fieldNode == null) {
|
||||
code.add(field.getAlias());
|
||||
} else {
|
||||
code.add(fieldNode.getAlias());
|
||||
}
|
||||
}
|
||||
|
||||
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||
ClassInfo declClass = field.getDeclClass();
|
||||
// TODO
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||
if (!fieldFromThisClass) {
|
||||
if (!fieldFromThisClass || !clsGen.isBodyGenStarted()) {
|
||||
// Android specific resources class handler
|
||||
ClassInfo parentClass = declClass.getParentClass();
|
||||
if (parentClass != null && parentClass.getShortName().equals("R")) {
|
||||
clsGen.useClass(code, parentClass);
|
||||
code.add('.');
|
||||
code.add(declClass.getAlias().getShortName());
|
||||
} else {
|
||||
if (!handleAppResField(code, clsGen, declClass)) {
|
||||
clsGen.useClass(code, declClass);
|
||||
}
|
||||
code.add('.');
|
||||
}
|
||||
FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
|
||||
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
code.add(field.getAlias());
|
||||
if (fieldNode == null) {
|
||||
code.add(field.getAlias());
|
||||
} else {
|
||||
code.add(fieldNode.getAlias());
|
||||
}
|
||||
}
|
||||
|
||||
protected void staticField(CodeWriter code, FieldInfo field) {
|
||||
@@ -198,40 +213,54 @@ public class InsnGen {
|
||||
mgen.getClassGen().useType(code, type);
|
||||
}
|
||||
|
||||
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
return makeInsn(insn, code, null);
|
||||
public void makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
makeInsn(insn, code, null);
|
||||
}
|
||||
|
||||
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
private static final Set<Flags> EMPTY_FLAGS = EnumSet.noneOf(Flags.class);
|
||||
private static final Set<Flags> BODY_ONLY_FLAG = EnumSet.of(Flags.BODY_ONLY);
|
||||
private static final Set<Flags> BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP);
|
||||
|
||||
protected void makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
if (insn.getType() == InsnType.REGION_ARG) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Set<Flags> state = EnumSet.noneOf(Flags.class);
|
||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||
state.add(flag);
|
||||
makeInsnBody(code, insn, state);
|
||||
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
|
||||
} else {
|
||||
if (flag != Flags.INLINE) {
|
||||
code.startLineWithNum(insn.getSourceLine());
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
}
|
||||
if (insn.contains(AFlag.COMMENT_OUT)) {
|
||||
code.add("// ");
|
||||
}
|
||||
}
|
||||
if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
assignVar(code, insn);
|
||||
code.add(" = ");
|
||||
RegisterArg resArg = insn.getResult();
|
||||
if (resArg != null) {
|
||||
SSAVar var = resArg.getSVar();
|
||||
if (var == null || var.getUseCount() != 0 || insn.getType() != InsnType.CONSTRUCTOR) {
|
||||
assignVar(code, insn);
|
||||
code.add(" = ");
|
||||
}
|
||||
}
|
||||
makeInsnBody(code, insn, state);
|
||||
makeInsnBody(code, insn, EMPTY_FLAGS);
|
||||
if (flag != Flags.INLINE) {
|
||||
code.add(';');
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
throw new CodegenException(mth, "Error generate insn: " + insn, th);
|
||||
} catch (Exception e) {
|
||||
throw new CodegenException(mth, "Error generate insn: " + insn, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
||||
switch (insn.getType()) {
|
||||
case CONST_STR:
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
code.add(StringUtils.unescapeString(str));
|
||||
code.add(mth.root().getStringUtils().unescapeString(str));
|
||||
break;
|
||||
|
||||
case CONST_CLASS:
|
||||
@@ -269,18 +298,14 @@ public class InsnGen {
|
||||
makeArith((ArithNode) insn, code, state);
|
||||
break;
|
||||
|
||||
case NEG: {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
code.add('-');
|
||||
addArg(code, insn.getArg(0));
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
case NEG:
|
||||
oneArgInsn(code, insn, state, '-');
|
||||
break;
|
||||
|
||||
case NOT:
|
||||
char op = insn.getArg(0).getType() == ArgType.BOOLEAN ? '!' : '~';
|
||||
oneArgInsn(code, insn, state, op);
|
||||
break;
|
||||
}
|
||||
|
||||
case RETURN:
|
||||
if (insn.getArgsCount() != 0) {
|
||||
@@ -365,6 +390,17 @@ public class InsnGen {
|
||||
filledNewArray((FilledNewArrayNode) insn, code);
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
FillArrayInsn arrayNode = (FillArrayInsn) insn;
|
||||
if (fallback) {
|
||||
String arrStr = arrayNode.dataToString();
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" = {").add(arrStr.substring(1, arrStr.length() - 1)).add("} // fill-array");
|
||||
} else {
|
||||
fillArray(code, arrayNode);
|
||||
}
|
||||
break;
|
||||
|
||||
case AGET:
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add('[');
|
||||
@@ -408,7 +444,7 @@ public class InsnGen {
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext();) {
|
||||
addArg(code, it.next());
|
||||
if (it.hasNext()) {
|
||||
code.add(" + ");
|
||||
@@ -430,7 +466,9 @@ public class InsnGen {
|
||||
case MONITOR_EXIT:
|
||||
if (isFallback()) {
|
||||
code.add("monitor-exit(");
|
||||
addArg(code, insn.getArg(0));
|
||||
if (insn.getArgsCount() == 1) {
|
||||
addArg(code, insn.getArg(0));
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
@@ -445,7 +483,7 @@ public class InsnGen {
|
||||
|
||||
/* fallback mode instructions */
|
||||
case IF:
|
||||
assert isFallback() : "if insn in not fallback mode";
|
||||
fallbackOnlyInsn(insn);
|
||||
IfNode ifInsn = (IfNode) insn;
|
||||
code.add("if (");
|
||||
addArg(code, insn.getArg(0));
|
||||
@@ -456,26 +494,27 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case GOTO:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
|
||||
break;
|
||||
|
||||
case MOVE_EXCEPTION:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("move-exception");
|
||||
break;
|
||||
|
||||
case SWITCH:
|
||||
assert isFallback();
|
||||
SwitchNode sw = (SwitchNode) insn;
|
||||
fallbackOnlyInsn(insn);
|
||||
SwitchInsn sw = (SwitchInsn) insn;
|
||||
code.add("switch(");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
for (int i = 0; i < sw.getCasesCount(); i++) {
|
||||
String key = sw.getKeys()[i].toString();
|
||||
code.startLine("case ").add(key).add(": goto ");
|
||||
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
|
||||
int[] keys = sw.getKeys();
|
||||
int[] targets = sw.getTargets();
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
||||
}
|
||||
code.startLine("default: goto ");
|
||||
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
||||
@@ -483,29 +522,35 @@ public class InsnGen {
|
||||
code.startLine('}');
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
assert isFallback();
|
||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||
Object data = arrayNode.getData();
|
||||
String arrStr;
|
||||
if (data instanceof int[]) {
|
||||
arrStr = Arrays.toString((int[]) data);
|
||||
} else if (data instanceof short[]) {
|
||||
arrStr = Arrays.toString((short[]) data);
|
||||
} else if (data instanceof byte[]) {
|
||||
arrStr = Arrays.toString((byte[]) data);
|
||||
} else if (data instanceof long[]) {
|
||||
arrStr = Arrays.toString((long[]) data);
|
||||
} else {
|
||||
arrStr = "?";
|
||||
}
|
||||
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
|
||||
break;
|
||||
|
||||
case NEW_INSTANCE:
|
||||
// only fallback - make new instance in constructor invoke
|
||||
assert isFallback();
|
||||
code.add("new " + insn.getResult().getType());
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("new ").add(insn.getResult().getInitType().toString());
|
||||
break;
|
||||
|
||||
case PHI:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add(insn.getType().toString()).add('(');
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
addArg(code, insnArg);
|
||||
code.add(' ');
|
||||
}
|
||||
code.add(')');
|
||||
break;
|
||||
|
||||
case MOVE_RESULT:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("move-result");
|
||||
break;
|
||||
|
||||
case FILL_ARRAY_DATA:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("fill-array " + insn.toString());
|
||||
break;
|
||||
|
||||
case SWITCH_DATA:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add(insn.toString());
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -513,9 +558,61 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In most cases must be combined with new array instructions.
|
||||
* Use one by one array fill (can be replaced with System.arrayCopy)
|
||||
*/
|
||||
private void fillArray(CodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
|
||||
code.add("// fill-array-data instruction");
|
||||
code.startLine();
|
||||
InsnArg arrArg = arrayNode.getArg(0);
|
||||
ArgType arrayType = arrArg.getType();
|
||||
ArgType elemType;
|
||||
if (arrayType.isTypeKnown() && arrayType.isArray()) {
|
||||
elemType = arrayType.getArrayElement();
|
||||
} else {
|
||||
ArgType elementType = arrayNode.getElementType(); // unknown type
|
||||
elemType = elementType.selectFirst();
|
||||
}
|
||||
List<LiteralArg> args = arrayNode.getLiteralArgs(elemType);
|
||||
int len = args.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
code.add(';');
|
||||
code.startLine();
|
||||
}
|
||||
addArg(code, arrArg);
|
||||
code.add('[').add(Integer.toString(i)).add("] = ").add(lit(args.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
private void oneArgInsn(CodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
code.add(op);
|
||||
addArg(code, insn.getArg(0));
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void fallbackOnlyInsn(InsnNode insn) throws CodegenException {
|
||||
if (!fallback) {
|
||||
String msg = insn.getType() + " instruction can be used only in fallback mode";
|
||||
CodegenException e = new CodegenException(msg);
|
||||
mth.addError(msg, e);
|
||||
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
|
||||
code.add("new ");
|
||||
useType(code, insn.getArrayType());
|
||||
if (!insn.contains(AFlag.DECLARE_VAR)) {
|
||||
code.add("new ");
|
||||
useType(code, insn.getArrayType());
|
||||
}
|
||||
code.add('{');
|
||||
int c = insn.getArgsCount();
|
||||
for (int i = 0; i < c; i++) {
|
||||
@@ -527,34 +624,12 @@ public class InsnGen {
|
||||
code.add('}');
|
||||
}
|
||||
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
|
||||
// anonymous class construction
|
||||
ArgType parent;
|
||||
if (cls.getInterfaces().size() == 1) {
|
||||
parent = cls.getInterfaces().get(0);
|
||||
} else {
|
||||
parent = cls.getSuperClass();
|
||||
}
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
MethodNode defCtr = cls.getDefaultConstructor();
|
||||
if (defCtr != null) {
|
||||
if (RegionUtils.notEmpty(defCtr.getRegion())) {
|
||||
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||
} else {
|
||||
defCtr.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
code.add("new ");
|
||||
if (parent == null) {
|
||||
code.add("Object");
|
||||
} else {
|
||||
useClass(code, parent);
|
||||
}
|
||||
code.add("() ");
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException {
|
||||
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||
cls.ensureProcessed();
|
||||
inlineAnonymousConstructor(code, cls, insn);
|
||||
mth.getParentClass().addInlinedClass(cls);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -567,21 +642,66 @@ public class InsnGen {
|
||||
} else {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
|
||||
if (genericInfoAttr != null) {
|
||||
code.add('<');
|
||||
if (genericInfoAttr.isExplicit()) {
|
||||
boolean first = true;
|
||||
for (ArgType type : genericInfoAttr.getGenericTypes()) {
|
||||
if (!first) {
|
||||
code.add(',');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
mgen.getClassGen().useType(code, type);
|
||||
}
|
||||
}
|
||||
code.add('>');
|
||||
}
|
||||
}
|
||||
generateMethodArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
|
||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
}
|
||||
|
||||
private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
if (this.mth.getParentClass() == cls) {
|
||||
cls.remove(AFlag.ANONYMOUS_CLASS);
|
||||
cls.remove(AFlag.DONT_GENERATE);
|
||||
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
||||
throw new CodegenException("Anonymous inner class unlimited recursion detected."
|
||||
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
|
||||
}
|
||||
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
ArgType parent;
|
||||
if (cls.getInterfaces().size() == 1) {
|
||||
parent = cls.getInterfaces().get(0);
|
||||
} else {
|
||||
parent = cls.getSuperClass();
|
||||
}
|
||||
// hide empty anonymous constructors
|
||||
for (MethodNode ctor : cls.getMethods()) {
|
||||
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
|
||||
&& RegionUtils.isEmpty(ctor.getRegion())) {
|
||||
ctor.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
|
||||
code.add("new ");
|
||||
if (parent == null) {
|
||||
code.add("Object");
|
||||
} else {
|
||||
useClass(code, parent);
|
||||
}
|
||||
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
code.add(' ');
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code, true);
|
||||
}
|
||||
|
||||
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
MethodInfo callMth = insn.getCallMth();
|
||||
|
||||
// inline method
|
||||
MethodNode callMthNode = mth.dex().deepResolveMethod(callMth);
|
||||
if (callMthNode != null) {
|
||||
if (inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
}
|
||||
callMth = callMthNode.getMethodInfo();
|
||||
}
|
||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
||||
|
||||
int k = 0;
|
||||
InvokeType type = insn.getInvokeType();
|
||||
@@ -598,13 +718,18 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case SUPER:
|
||||
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
|
||||
if (superCallCls != null) {
|
||||
useClass(code, superCallCls);
|
||||
code.add('.');
|
||||
}
|
||||
// use 'super' instead 'this' in 0 arg
|
||||
code.add("super").add('.');
|
||||
k++;
|
||||
break;
|
||||
|
||||
case STATIC:
|
||||
ClassInfo insnCls = mth.getParentClass().getAlias();
|
||||
ClassInfo insnCls = mth.getParentClass().getClassInfo();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (!insnCls.equals(declClass)) {
|
||||
useClass(code, declClass);
|
||||
@@ -614,114 +739,97 @@ public class InsnGen {
|
||||
}
|
||||
if (callMthNode != null) {
|
||||
code.attachAnnotation(callMthNode);
|
||||
code.add(callMthNode.getAlias());
|
||||
} else {
|
||||
code.add(callMth.getAlias());
|
||||
}
|
||||
code.add(callMth.getAlias());
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
||||
@Nullable MethodNode callMth) throws CodegenException {
|
||||
@Nullable
|
||||
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
|
||||
ClassNode useCls = mth.getParentClass();
|
||||
ClassInfo insnCls = useCls.getClassInfo();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (insnCls.equals(declClass)) {
|
||||
return null;
|
||||
}
|
||||
ClassNode topClass = useCls.getTopParentClass();
|
||||
if (topClass.getClassInfo().equals(declClass)) {
|
||||
return declClass;
|
||||
}
|
||||
// search call class
|
||||
ClassNode nextParent = useCls;
|
||||
do {
|
||||
ClassInfo nextClsInfo = nextParent.getClassInfo();
|
||||
if (nextClsInfo.equals(declClass)
|
||||
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
|
||||
if (nextParent == useCls) {
|
||||
return null;
|
||||
}
|
||||
return nextClsInfo;
|
||||
}
|
||||
nextParent = nextParent.getParentClass();
|
||||
} while (nextParent != null && nextParent != topClass);
|
||||
|
||||
// search failed, just return parent class
|
||||
return useCls.getParentClass().getClassInfo();
|
||||
}
|
||||
|
||||
void generateMethodArguments(CodeWriter code, BaseInvokeNode insn, int startArgNum,
|
||||
@Nullable MethodNode mthNode) throws CodegenException {
|
||||
int k = startArgNum;
|
||||
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
k++;
|
||||
}
|
||||
int argsCount = insn.getArgsCount();
|
||||
code.add('(');
|
||||
boolean firstArg = true;
|
||||
if (k < argsCount) {
|
||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||
for (int i = k; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
|
||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
||||
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
int argOrigPos = i - startArgNum;
|
||||
if (SkipMethodArgsAttr.isSkip(mthNode, argOrigPos)) {
|
||||
continue;
|
||||
}
|
||||
if (!firstArg) {
|
||||
code.add(", ");
|
||||
} else {
|
||||
firstArg = false;
|
||||
}
|
||||
if (i == argsCount - 1 && processVarArg(code, insn, arg)) {
|
||||
continue;
|
||||
}
|
||||
addArg(code, arg, false);
|
||||
if (i < argsCount - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
firstArg = false;
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional cast for overloaded method argument.
|
||||
*/
|
||||
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
|
||||
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
|
||||
if (!arg.getType().equals(origType)) {
|
||||
code.add('(');
|
||||
useType(code, origType);
|
||||
code.add(") ");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand varArgs from filled array.
|
||||
*/
|
||||
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
|
||||
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
|
||||
private boolean processVarArg(CodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
|
||||
if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
|
||||
return false;
|
||||
}
|
||||
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
||||
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
|
||||
if (mia == null) {
|
||||
if (insn.getType() != InsnType.FILLED_NEW_ARRAY) {
|
||||
return false;
|
||||
}
|
||||
InsnNode inl = mia.getInsn();
|
||||
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
} else {
|
||||
// remap args
|
||||
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
|
||||
List<RegisterArg> callArgs = callMthNode.getArguments(true);
|
||||
for (int i = 0; i < callArgs.size(); i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
RegisterArg callArg = callArgs.get(i);
|
||||
regs[callArg.getRegNum()] = arg;
|
||||
}
|
||||
// replace args
|
||||
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
|
||||
inl.getRegisterArgs(inlArgs);
|
||||
Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
|
||||
for (RegisterArg r : inlArgs) {
|
||||
int regNum = r.getRegNum();
|
||||
if (regNum >= regs.length) {
|
||||
LOG.warn("Unknown register number {} in method call: {} from {}", r, callMthNode, mth);
|
||||
} else {
|
||||
InsnArg repl = regs[regNum];
|
||||
if (repl == null) {
|
||||
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
|
||||
} else {
|
||||
inl.replaceArg(r, repl);
|
||||
toRevert.put(r, repl);
|
||||
}
|
||||
}
|
||||
}
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
// revert changes in 'MethodInlineAttr'
|
||||
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
|
||||
inl.replaceArg(e.getValue(), e.getKey());
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -735,11 +843,12 @@ public class InsnGen {
|
||||
InsnArg first = insn.getArg(0);
|
||||
InsnArg second = insn.getArg(1);
|
||||
ConditionGen condGen = new ConditionGen(this);
|
||||
if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
|
||||
if (first.isTrue() && second.isFalse()) {
|
||||
condGen.add(code, insn.getCondition());
|
||||
} else {
|
||||
condGen.wrap(code, insn.getCondition());
|
||||
code.add(" ? ");
|
||||
addCastIfNeeded(code, first, second);
|
||||
addArg(code, first, false);
|
||||
code.add(" : ");
|
||||
addArg(code, second, false);
|
||||
@@ -749,6 +858,33 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void addCastIfNeeded(CodeWriter code, InsnArg first, InsnArg second) {
|
||||
if (first.isLiteral() && second.isLiteral()) {
|
||||
if (first.getType() == ArgType.BYTE) {
|
||||
long lit1 = ((LiteralArg) first).getLiteral();
|
||||
long lit2 = ((LiteralArg) second).getLiteral();
|
||||
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
|
||||
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
|
||||
code.add("(byte) ");
|
||||
}
|
||||
} else if (first.getType() == ArgType.SHORT) {
|
||||
long lit1 = ((LiteralArg) first).getLiteral();
|
||||
long lit2 = ((LiteralArg) second).getLiteral();
|
||||
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
|
||||
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
|
||||
code.add("(short) ");
|
||||
}
|
||||
} else if (first.getType() == ArgType.CHAR) {
|
||||
long lit1 = ((LiteralArg) first).getLiteral();
|
||||
long lit2 = ((LiteralArg) second).getLiteral();
|
||||
if (!NameMapper.isPrintableChar((char) (lit1))
|
||||
&& !NameMapper.isPrintableChar((char) (lit2))) {
|
||||
code.add("(char) ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
makeArithOneArg(insn, code);
|
||||
@@ -771,19 +907,22 @@ public class InsnGen {
|
||||
|
||||
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
|
||||
ArithOp op = insn.getOp();
|
||||
InsnArg resArg = insn.getArg(0);
|
||||
InsnArg arg = insn.getArg(1);
|
||||
|
||||
// "++" or "--"
|
||||
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
||||
LiteralArg lit = (LiteralArg) arg;
|
||||
if (lit.isInteger() && lit.getLiteral() == 1) {
|
||||
assignVar(code, insn);
|
||||
if (lit.getLiteral() == 1 && lit.isInteger()) {
|
||||
addArg(code, resArg, false);
|
||||
String opSymbol = op.getSymbol();
|
||||
code.add(opSymbol).add(opSymbol);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// +=, -= ...
|
||||
assignVar(code, insn);
|
||||
|
||||
// +=, -=, ...
|
||||
addArg(code, resArg, false);
|
||||
code.add(' ').add(op.getSymbol()).add("= ");
|
||||
addArg(code, arg, false);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,45 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.COMMENTED_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.FALLBACK_MODE;
|
||||
|
||||
public class MethodGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||
@@ -56,8 +70,8 @@ public class MethodGen {
|
||||
|
||||
public boolean addDefinition(CodeWriter code) {
|
||||
if (mth.getMethodInfo().isClassInit()) {
|
||||
code.startLine("static");
|
||||
code.attachDefinition(mth);
|
||||
code.startLine("static");
|
||||
return true;
|
||||
}
|
||||
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||
@@ -66,181 +80,329 @@ public class MethodGen {
|
||||
code.attachDefinition(mth);
|
||||
return false;
|
||||
}
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
ClassGen.addMthUsageInfo(code, mth);
|
||||
}
|
||||
addOverrideAnnotation(code, mth);
|
||||
annotationGen.addForMethod(code, mth);
|
||||
|
||||
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
|
||||
AccessInfo ai = mth.getAccessFlags();
|
||||
// don't add 'abstract' and 'public' to methods in interface
|
||||
if (clsAccFlags.isInterface()) {
|
||||
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
ai = ai.remove(AccessFlags.ABSTRACT);
|
||||
ai = ai.remove(AccessFlags.PUBLIC);
|
||||
}
|
||||
// don't add 'public' for annotations
|
||||
if (clsAccFlags.isAnnotation()) {
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
ai = ai.remove(AccessFlags.PUBLIC);
|
||||
}
|
||||
|
||||
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
|
||||
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
|
||||
}
|
||||
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
|
||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||
}
|
||||
|
||||
code.startLineWithNum(mth.getSourceLine());
|
||||
code.add(ai.makeString());
|
||||
if (Consts.DEBUG) {
|
||||
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
|
||||
}
|
||||
if (clsAccFlags.isInterface() && !mth.isNoCode()) {
|
||||
// add 'default' for method with code in interface
|
||||
code.add("default ");
|
||||
}
|
||||
|
||||
if (classGen.addGenericMap(code, mth.getGenericMap())) {
|
||||
if (classGen.addGenericTypeParameters(code, mth.getTypeParameters(), false)) {
|
||||
code.add(' ');
|
||||
}
|
||||
if (mth.getAccessFlags().isConstructor()) {
|
||||
if (ai.isConstructor()) {
|
||||
code.attachDefinition(mth);
|
||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||
} else {
|
||||
classGen.useType(code, mth.getReturnType());
|
||||
code.add(' ');
|
||||
code.attachDefinition(mth);
|
||||
code.add(mth.getAlias());
|
||||
}
|
||||
code.add('(');
|
||||
|
||||
List<RegisterArg> args = mth.getArguments(false);
|
||||
List<RegisterArg> args = mth.getArgRegs();
|
||||
if (mth.getMethodInfo().isConstructor()
|
||||
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
|
||||
if (args.size() == 2) {
|
||||
args.clear();
|
||||
args = Collections.emptyList();
|
||||
} else if (args.size() > 2) {
|
||||
args = args.subList(2, args.size());
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
|
||||
"Incorrect number of args for enum constructor: " + args.size()
|
||||
+ " (expected >= 2)"
|
||||
));
|
||||
mth.addComment("JADX WARN: Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
|
||||
}
|
||||
} else if (mth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
args = args.subList(1, args.size());
|
||||
}
|
||||
addMethodArguments(code, args);
|
||||
code.add(')');
|
||||
|
||||
annotationGen.addThrows(mth, code);
|
||||
code.attachDefinition(mth);
|
||||
|
||||
// add default value if in annotation class
|
||||
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
|
||||
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||
if (def != null) {
|
||||
code.add(" default ");
|
||||
annotationGen.encodeValue(mth.root(), code, def);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
|
||||
private void addOverrideAnnotation(CodeWriter code, MethodNode mth) {
|
||||
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
|
||||
if (overrideAttr == null) {
|
||||
return;
|
||||
}
|
||||
code.startLine("@Override");
|
||||
code.add(" // ");
|
||||
Iterator<IMethodDetails> it = overrideAttr.getOverrideList().iterator();
|
||||
while (it.hasNext()) {
|
||||
IMethodDetails methodDetails = it.next();
|
||||
code.add(methodDetails.getMethodInfo().getDeclClass().getAliasFullName());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) {
|
||||
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
||||
int i = 0;
|
||||
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
|
||||
RegisterArg arg = it.next();
|
||||
Iterator<RegisterArg> it = args.iterator();
|
||||
while (it.hasNext()) {
|
||||
RegisterArg mthArg = it.next();
|
||||
SSAVar ssaVar = mthArg.getSVar();
|
||||
CodeVar var;
|
||||
if (ssaVar == null) {
|
||||
// null for abstract or interface methods
|
||||
var = CodeVar.fromMthArg(mthArg, classGen.isFallbackMode());
|
||||
} else {
|
||||
var = ssaVar.getCodeVar();
|
||||
}
|
||||
|
||||
// add argument annotation
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
|
||||
annotationGen.addForParameter(code, paramsAnnotation, i);
|
||||
}
|
||||
if (var.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
ArgType argType;
|
||||
ArgType varType = var.getType();
|
||||
if (varType == null || varType == ArgType.UNKNOWN) {
|
||||
// occur on decompilation errors
|
||||
argType = mthArg.getInitType();
|
||||
} else {
|
||||
argType = varType;
|
||||
}
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
ArgType type = arg.getType();
|
||||
if (type.isArray()) {
|
||||
ArgType elType = type.getArrayElement();
|
||||
classGen.useType(argsCode, elType);
|
||||
argsCode.add("...");
|
||||
if (argType.isArray()) {
|
||||
ArgType elType = argType.getArrayElement();
|
||||
classGen.useType(code, elType);
|
||||
code.add("...");
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
|
||||
classGen.useType(argsCode, arg.getType());
|
||||
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var);
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
} else {
|
||||
classGen.useType(argsCode, arg.getType());
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
argsCode.add(' ');
|
||||
argsCode.add(nameGen.assignArg(arg));
|
||||
code.add(' ');
|
||||
code.add(nameGen.assignArg(var));
|
||||
|
||||
i++;
|
||||
if (it.hasNext()) {
|
||||
argsCode.add(", ");
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addInstructions(CodeWriter code) throws CodegenException {
|
||||
if (mth.contains(AType.JADX_ERROR)
|
||||
|| mth.contains(AFlag.INCONSISTENT_CODE)
|
||||
|| mth.getRegion() == null) {
|
||||
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
|
||||
if (err != null) {
|
||||
code.startLine("/* JADX: method processing error */");
|
||||
Throwable cause = err.getCause();
|
||||
if (cause != null) {
|
||||
code.newLine();
|
||||
code.add("/*");
|
||||
code.startLine("Error: ").add(Utils.getStackTrace(cause));
|
||||
code.add("*/");
|
||||
}
|
||||
}
|
||||
code.startLine("/*");
|
||||
addFallbackMethodCode(code);
|
||||
code.startLine("*/");
|
||||
|
||||
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
||||
.add(mth.toString())
|
||||
.add("\");");
|
||||
if (mth.root().getArgs().isFallbackMode()) {
|
||||
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||
} else if (classGen.isFallbackMode()) {
|
||||
dumpInstructions(code);
|
||||
} else {
|
||||
RegionGen regionGen = new RegionGen(this);
|
||||
regionGen.makeRegion(code, mth.getRegion());
|
||||
addRegionInsns(code);
|
||||
}
|
||||
}
|
||||
|
||||
public void addFallbackMethodCode(CodeWriter code) {
|
||||
if (mth.getInstructions() == null) {
|
||||
JadxErrorAttr errorAttr = mth.get(AType.JADX_ERROR);
|
||||
if (errorAttr == null
|
||||
|| errorAttr.getCause() == null
|
||||
|| !errorAttr.getCause().getClass().equals(DecodeException.class)) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.load();
|
||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
public void addRegionInsns(CodeWriter code) throws CodegenException {
|
||||
try {
|
||||
RegionGen regionGen = new RegionGen(this);
|
||||
regionGen.makeRegion(code, mth.getRegion());
|
||||
} catch (StackOverflowError | BootstrapMethodError e) {
|
||||
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
|
||||
classGen.insertDecompilationProblems(code, mth);
|
||||
dumpInstructions(code);
|
||||
} catch (Exception e) {
|
||||
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||
throw e;
|
||||
}
|
||||
mth.addError("Method code generation error", e);
|
||||
classGen.insertDecompilationProblems(code, mth);
|
||||
dumpInstructions(code);
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpInstructions(CodeWriter code) {
|
||||
code.startLine("/*");
|
||||
addFallbackMethodCode(code, COMMENTED_DUMP);
|
||||
code.startLine("*/");
|
||||
|
||||
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
||||
.add(mth.getParentClass().getClassInfo().getAliasFullName())
|
||||
.add('.')
|
||||
.add(mth.getAlias())
|
||||
.add('(')
|
||||
.add(Utils.listToString(mth.getMethodInfo().getArgumentsTypes()))
|
||||
.add("):")
|
||||
.add(mth.getMethodInfo().getReturnType().toString())
|
||||
.add("\");");
|
||||
}
|
||||
|
||||
public void addFallbackMethodCode(CodeWriter code, FallbackOption fallbackOption) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
|
||||
DepthTraversal.visit(visitor, mth);
|
||||
}
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
InsnNode[] insnArr = mth.getInstructions();
|
||||
if (insnArr == null) {
|
||||
code.startLine("// Can't load method instructions.");
|
||||
return;
|
||||
}
|
||||
if (insnArr.length > 100) {
|
||||
code.startLine("// Method dump skipped, instructions count: " + insnArr.length);
|
||||
return;
|
||||
}
|
||||
code.incIndent();
|
||||
if (mth.getThisArg() != null) {
|
||||
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
||||
}
|
||||
addFallbackInsns(code, mth, insnArr, true);
|
||||
addFallbackInsns(code, mth, insnArr, fallbackOption);
|
||||
code.decIndent();
|
||||
}
|
||||
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
|
||||
public enum FallbackOption {
|
||||
FALLBACK_MODE,
|
||||
BLOCK_DUMP,
|
||||
COMMENTED_DUMP
|
||||
}
|
||||
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
||||
int startIndent = code.getIndent();
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
boolean attachInsns = mth.root().getArgs().isJsonOutput();
|
||||
InsnNode prevInsn = null;
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn == null || insn.getType() == InsnType.NOP) {
|
||||
if (insn == null) {
|
||||
continue;
|
||||
}
|
||||
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
|
||||
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
|
||||
code.decIndent();
|
||||
code.startLine(getLabelName(insn.getOffset()) + ":");
|
||||
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||
code.incIndent();
|
||||
}
|
||||
if (insn.getType() == InsnType.NOP) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (insnGen.makeInsn(insn, code)) {
|
||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
code.add("\t " + catchAttr);
|
||||
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
||||
if (escapeComment) {
|
||||
code.decIndent();
|
||||
code.startLine("*/");
|
||||
code.startLine("// ");
|
||||
} else {
|
||||
code.startLine();
|
||||
}
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
}
|
||||
RegisterArg resArg = insn.getResult();
|
||||
if (resArg != null) {
|
||||
ArgType varType = resArg.getInitType();
|
||||
if (varType.isTypeKnown()) {
|
||||
code.add(varType.toString()).add(' ');
|
||||
}
|
||||
}
|
||||
} catch (CodegenException e) {
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
||||
if (escapeComment) {
|
||||
code.startLine("/*");
|
||||
code.incIndent();
|
||||
}
|
||||
|
||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
code.add(" // " + catchAttr);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||
code.setIndent(startIndent);
|
||||
code.startLine("// error: " + insn);
|
||||
}
|
||||
prevInsn = insn;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCommentEscapeNeeded(InsnNode insn, FallbackOption option) {
|
||||
if (option == COMMENTED_DUMP) {
|
||||
if (insn.getType() == InsnType.CONST_STR) {
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
return str.contains("*/");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean needLabel(InsnNode insn, InsnNode prevInsn) {
|
||||
if (insn.contains(AType.EXC_HANDLER)) {
|
||||
return true;
|
||||
}
|
||||
if (insn.contains(AType.JUMP)) {
|
||||
// don't add label for ifs else branch
|
||||
if (prevInsn != null && prevInsn.getType() == InsnType.IF) {
|
||||
List<JumpInfo> jumps = insn.getAll(AType.JUMP);
|
||||
if (jumps.size() == 1) {
|
||||
JumpInfo jump = jumps.get(0);
|
||||
if (jump.getSrc() == prevInsn.getOffset() && jump.getDest() == insn.getOffset()) {
|
||||
int target = ((IfNode) prevInsn).getTarget();
|
||||
return insn.getOffset() == target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fallback variant of method codegen
|
||||
*/
|
||||
public static MethodGen getFallbackMethodGen(MethodNode mth) {
|
||||
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true);
|
||||
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, false, true, true);
|
||||
return new MethodGen(clsGen, mth);
|
||||
}
|
||||
|
||||
public static String getLabelName(int offset) {
|
||||
return "L_" + InsnUtils.formatOffset(offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
@@ -7,58 +12,74 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class NameGen {
|
||||
|
||||
private static final Map<String, String> OBJ_ALIAS;
|
||||
|
||||
private final Set<String> varNames = new HashSet<String>();
|
||||
private final Set<String> varNames = new HashSet<>();
|
||||
private final MethodNode mth;
|
||||
private final boolean fallback;
|
||||
|
||||
static {
|
||||
OBJ_ALIAS = new HashMap<String, String>();
|
||||
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
|
||||
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
|
||||
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
|
||||
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
|
||||
OBJ_ALIAS.put("java.util.Iterator", "it");
|
||||
OBJ_ALIAS.put("java.lang.Boolean", "bool");
|
||||
OBJ_ALIAS.put("java.lang.Short", "sh");
|
||||
OBJ_ALIAS.put("java.lang.Integer", "num");
|
||||
OBJ_ALIAS.put("java.lang.Character", "ch");
|
||||
OBJ_ALIAS.put("java.lang.Byte", "b");
|
||||
OBJ_ALIAS.put("java.lang.Float", "f");
|
||||
OBJ_ALIAS.put("java.lang.Long", "l");
|
||||
OBJ_ALIAS.put("java.lang.Double", "d");
|
||||
OBJ_ALIAS = Utils.newConstStringMap(
|
||||
Consts.CLASS_STRING, "str",
|
||||
Consts.CLASS_CLASS, "cls",
|
||||
Consts.CLASS_THROWABLE, "th",
|
||||
Consts.CLASS_OBJECT, "obj",
|
||||
"java.util.Iterator", "it",
|
||||
"java.lang.Boolean", "bool",
|
||||
"java.lang.Short", "sh",
|
||||
"java.lang.Integer", "num",
|
||||
"java.lang.Character", "ch",
|
||||
"java.lang.Byte", "b",
|
||||
"java.lang.Float", "f",
|
||||
"java.lang.Long", "l",
|
||||
"java.lang.Double", "d",
|
||||
"java.lang.StringBuilder", "sb",
|
||||
"java.lang.Exception", "exc");
|
||||
}
|
||||
|
||||
public NameGen(MethodNode mth, boolean fallback) {
|
||||
this.mth = mth;
|
||||
this.fallback = fallback;
|
||||
addNamesUsedInClass();
|
||||
}
|
||||
|
||||
public String assignArg(RegisterArg arg) {
|
||||
String name = makeArgName(arg);
|
||||
if (fallback) {
|
||||
return name;
|
||||
private void addNamesUsedInClass() {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
for (FieldNode field : parentClass.getFields()) {
|
||||
varNames.add(field.getAlias());
|
||||
}
|
||||
name = getUniqueVarName(name);
|
||||
arg.setName(name);
|
||||
for (ClassNode innerClass : parentClass.getInnerClasses()) {
|
||||
varNames.add(innerClass.getClassInfo().getAliasShortName());
|
||||
}
|
||||
// add all root package names to avoid collisions with full class names
|
||||
varNames.addAll(mth.root().getCacheStorage().getRootPkgs());
|
||||
}
|
||||
|
||||
public String assignArg(CodeVar var) {
|
||||
if (fallback) {
|
||||
return getFallbackName(var);
|
||||
}
|
||||
if (var.isThis()) {
|
||||
return RegisterArg.THIS_ARG_NAME;
|
||||
}
|
||||
String name = getUniqueVarName(makeArgName(var));
|
||||
var.setName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -98,54 +119,60 @@ public class NameGen {
|
||||
return r;
|
||||
}
|
||||
|
||||
private String makeArgName(RegisterArg arg) {
|
||||
if (fallback) {
|
||||
return getFallbackName(arg);
|
||||
private String makeArgName(CodeVar var) {
|
||||
String name = var.getName();
|
||||
if (name == null) {
|
||||
name = guessName(var);
|
||||
}
|
||||
String name = arg.getName();
|
||||
String varName;
|
||||
if (name != null) {
|
||||
if ("this".equals(name)) {
|
||||
return name;
|
||||
}
|
||||
varName = name;
|
||||
} else {
|
||||
varName = guessName(arg);
|
||||
if (!NameMapper.isValidAndPrintable(name)) {
|
||||
name = getFallbackName(var);
|
||||
}
|
||||
if (NameMapper.isReserved(varName)) {
|
||||
return varName + "R";
|
||||
if (Consts.DEBUG) {
|
||||
name += '_' + getFallbackName(var);
|
||||
}
|
||||
return varName;
|
||||
return name;
|
||||
}
|
||||
|
||||
private String getFallbackName(CodeVar var) {
|
||||
List<SSAVar> ssaVars = var.getSsaVars();
|
||||
if (ssaVars.isEmpty()) {
|
||||
return "v";
|
||||
}
|
||||
return getFallbackName(ssaVars.get(0).getAssign());
|
||||
}
|
||||
|
||||
private String getFallbackName(RegisterArg arg) {
|
||||
return "r" + arg.getRegNum();
|
||||
}
|
||||
|
||||
private String guessName(RegisterArg arg) {
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar != null && sVar.getName() == null) {
|
||||
RegisterArg assignArg = sVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
if (assignInsn != null) {
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
return name;
|
||||
private String guessName(CodeVar var) {
|
||||
List<SSAVar> ssaVars = var.getSsaVars();
|
||||
if (ssaVars != null && !ssaVars.isEmpty()) {
|
||||
// TODO: use all vars for better name generation
|
||||
SSAVar ssaVar = ssaVars.get(0);
|
||||
if (ssaVar != null && ssaVar.getName() == null) {
|
||||
RegisterArg assignArg = ssaVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
if (assignInsn != null) {
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return makeNameForType(arg.getType());
|
||||
return makeNameForType(var.getType());
|
||||
}
|
||||
|
||||
private String makeNameForType(ArgType type) {
|
||||
if (type.isPrimitive()) {
|
||||
return makeNameForPrimitive(type);
|
||||
} else if (type.isArray()) {
|
||||
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||
} else {
|
||||
return makeNameForObject(type);
|
||||
}
|
||||
if (type.isArray()) {
|
||||
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||
}
|
||||
return makeNameForObject(type);
|
||||
}
|
||||
|
||||
private static String makeNameForPrimitive(ArgType type) {
|
||||
@@ -153,17 +180,23 @@ public class NameGen {
|
||||
}
|
||||
|
||||
private String makeNameForObject(ArgType type) {
|
||||
if (type.isGenericType()) {
|
||||
return StringUtils.escape(type.getObject().toLowerCase());
|
||||
}
|
||||
if (type.isObject()) {
|
||||
String alias = getAliasForObject(type.getObject());
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
|
||||
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
return vName;
|
||||
}
|
||||
if (shortName != null) {
|
||||
return StringUtils.escape(shortName.toLowerCase());
|
||||
}
|
||||
}
|
||||
return StringUtils.escape(type.toString());
|
||||
}
|
||||
@@ -228,16 +261,19 @@ public class NameGen {
|
||||
if (name.startsWith("get") || name.startsWith("set")) {
|
||||
return fromName(name.substring(3));
|
||||
}
|
||||
ArgType declType = callMth.getDeclClass().getAlias().getType();
|
||||
if ("iterator".equals(name)) {
|
||||
return "it";
|
||||
}
|
||||
ArgType declType = callMth.getDeclClass().getType();
|
||||
if ("toString".equals(name)) {
|
||||
return makeNameForType(declType);
|
||||
}
|
||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||
}
|
||||
if (name.startsWith("to")) {
|
||||
return fromName(name.substring(2));
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.FieldInitAttr;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.SwitchInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -15,9 +27,9 @@ import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SwitchRegion.CaseInfo;
|
||||
import jadx.core.dex.regions.SynchronizedRegion;
|
||||
import jadx.core.dex.regions.TryCatchRegion;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
@@ -27,17 +39,11 @@ import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RegionGen extends InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
|
||||
|
||||
@@ -73,7 +79,7 @@ public class RegionGen extends InsnGen {
|
||||
private void declareVars(CodeWriter code, IContainer cont) {
|
||||
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
||||
if (declVars != null) {
|
||||
for (RegisterArg v : declVars.getVars()) {
|
||||
for (CodeVar v : declVars.getVars()) {
|
||||
code.startLine();
|
||||
declareVar(code, v);
|
||||
code.add(';');
|
||||
@@ -95,8 +101,12 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (!insn.contains(AFlag.SKIP)) {
|
||||
if (!insn.contains(AFlag.DONT_GENERATE)) {
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
@@ -112,21 +122,44 @@ public class RegionGen extends InsnGen {
|
||||
} else {
|
||||
code.attachSourceLine(region.getSourceLine());
|
||||
}
|
||||
if (attachInsns) {
|
||||
List<BlockNode> conditionBlocks = region.getConditionBlocks();
|
||||
if (!conditionBlocks.isEmpty()) {
|
||||
BlockNode blockNode = conditionBlocks.get(0);
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||
if (lastInsn != null) {
|
||||
code.attachLineAnnotation(lastInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean comment = region.contains(AFlag.COMMENT_OUT);
|
||||
if (comment) {
|
||||
code.add("// ");
|
||||
}
|
||||
|
||||
code.add("if (");
|
||||
new ConditionGen(this).add(code, region.getCondition());
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getThenRegion());
|
||||
code.startLine('}');
|
||||
if (comment) {
|
||||
code.startLine("// }");
|
||||
} else {
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
IContainer els = region.getElseRegion();
|
||||
if (els != null && RegionUtils.notEmpty(els)) {
|
||||
if (RegionUtils.notEmpty(els)) {
|
||||
code.add(" else ");
|
||||
if (connectElseIf(code, els)) {
|
||||
return;
|
||||
}
|
||||
code.add('{');
|
||||
makeRegionIndent(code, els);
|
||||
code.startLine('}');
|
||||
if (comment) {
|
||||
code.startLine("// }");
|
||||
} else {
|
||||
code.startLine('}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,34 +167,21 @@ public class RegionGen extends InsnGen {
|
||||
* Connect if-else-if block
|
||||
*/
|
||||
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
|
||||
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
|
||||
return false;
|
||||
}
|
||||
if (!(els instanceof Region)) {
|
||||
return false;
|
||||
}
|
||||
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
||||
if (subBlocks.size() == 1
|
||||
&& subBlocks.get(0) instanceof IfRegion) {
|
||||
makeIf((IfRegion) subBlocks.get(0), code, false);
|
||||
return true;
|
||||
if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) {
|
||||
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
||||
if (subBlocks.size() == 1) {
|
||||
IContainer elseBlock = subBlocks.get(0);
|
||||
if (elseBlock instanceof IfRegion) {
|
||||
declareVars(code, elseBlock);
|
||||
makeIf((IfRegion) elseBlock, code, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
|
||||
BlockNode header = region.getHeader();
|
||||
if (header != null) {
|
||||
List<InsnNode> headerInsns = header.getInstructions();
|
||||
if (headerInsns.size() > 1) {
|
||||
ErrorsCounter.methodError(mth, "Found not inlined instructions from loop header");
|
||||
int last = headerInsns.size() - 1;
|
||||
for (int i = 0; i < last; i++) {
|
||||
InsnNode insn = headerInsns.get(i);
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||
if (labelAttr != null) {
|
||||
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
||||
@@ -207,11 +227,13 @@ public class RegionGen extends InsnGen {
|
||||
if (region.isConditionAtEnd()) {
|
||||
code.startLine("do {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine("} while (");
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("} while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(");");
|
||||
} else {
|
||||
code.startLine("while (");
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
@@ -229,49 +251,56 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
|
||||
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
|
||||
SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader());
|
||||
Objects.requireNonNull(insn, "Switch insn not found in header");
|
||||
InsnArg arg = insn.getArg(0);
|
||||
code.startLine("switch (");
|
||||
addArg(code, arg, false);
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
|
||||
int size = sw.getKeys().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
List<Object> keys = sw.getKeys().get(i);
|
||||
IContainer c = sw.getCases().get(i);
|
||||
for (CaseInfo caseInfo : sw.getCases()) {
|
||||
List<Object> keys = caseInfo.getKeys();
|
||||
IContainer c = caseInfo.getContainer();
|
||||
for (Object k : keys) {
|
||||
code.startLine("case ");
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
code.add(fn.getAlias());
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replace with incorrect field
|
||||
FieldValueAttr valueAttr = fn.get(AType.FIELD_VALUE);
|
||||
if (valueAttr != null && valueAttr.getValue() != null) {
|
||||
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
|
||||
}
|
||||
}
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
|
||||
if (k == SwitchRegion.DEFAULT_CASE_KEY) {
|
||||
code.startLine("default:");
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
code.startLine("case ");
|
||||
addCaseKey(code, arg, k);
|
||||
code.add(':');
|
||||
}
|
||||
code.add(':');
|
||||
}
|
||||
makeRegionIndent(code, c);
|
||||
}
|
||||
if (sw.getDefaultCase() != null) {
|
||||
code.startLine("default:");
|
||||
makeRegionIndent(code, sw.getDefaultCase());
|
||||
}
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
|
||||
private void addCaseKey(CodeWriter code, InsnArg arg, Object k) {
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
code.add(fn.getAlias());
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replaced with incorrect field
|
||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
||||
if (valueAttr != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) {
|
||||
Object value = valueAttr.getEncodedValue();
|
||||
if (value != null) {
|
||||
code.add(" /*").add(value.toString()).add("*/");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
}
|
||||
|
||||
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
|
||||
code.startLine("try {");
|
||||
makeRegionIndent(code, region.getTryRegion());
|
||||
@@ -305,17 +334,29 @@ public class RegionGen extends InsnGen {
|
||||
return;
|
||||
}
|
||||
code.startLine("} catch (");
|
||||
InsnArg arg = handler.getArg();
|
||||
if (arg instanceof RegisterArg) {
|
||||
declareVar(code, (RegisterArg) arg);
|
||||
} else if (arg instanceof NamedArg) {
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
} else {
|
||||
useClass(code, handler.getCatchType());
|
||||
if (handler.isCatchAll()) {
|
||||
useClass(code, ArgType.THROWABLE);
|
||||
} else {
|
||||
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
|
||||
if (it.hasNext()) {
|
||||
useClass(code, it.next());
|
||||
}
|
||||
code.add(' ');
|
||||
while (it.hasNext()) {
|
||||
code.add(" | ");
|
||||
useClass(code, it.next());
|
||||
}
|
||||
}
|
||||
code.add(' ');
|
||||
InsnArg arg = handler.getArg();
|
||||
if (arg == null) {
|
||||
code.add("unknown"); // throwing exception is too late at this point
|
||||
} else if (arg instanceof RegisterArg) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
|
||||
}
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.IDexNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class TypeGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
|
||||
|
||||
@@ -26,18 +29,39 @@ public class TypeGen {
|
||||
return stype.getShortName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert literal arg to string (preferred method)
|
||||
*/
|
||||
public static String literalToString(LiteralArg arg, IDexNode dexNode, boolean fallback) {
|
||||
return literalToString(arg.getLiteral(), arg.getType(),
|
||||
dexNode.root().getStringUtils(),
|
||||
fallback,
|
||||
arg.contains(AFlag.EXPLICIT_PRIMITIVE_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert literal value to string according to value type
|
||||
*
|
||||
* @throws JadxRuntimeException for incorrect type or literal value
|
||||
*/
|
||||
public static String literalToString(long lit, ArgType type) {
|
||||
public static String literalToString(long lit, ArgType type, IDexNode dexNode, boolean fallback) {
|
||||
return literalToString(lit, type, dexNode.root().getStringUtils(), fallback, false);
|
||||
}
|
||||
|
||||
public static String literalToString(long lit, ArgType type, StringUtils stringUtils, boolean fallback, boolean cast) {
|
||||
if (type == null || !type.isTypeKnown()) {
|
||||
String n = Long.toString(lit);
|
||||
if (Math.abs(lit) > 100) {
|
||||
n += "; // 0x" + Long.toHexString(lit)
|
||||
+ " float:" + Float.intBitsToFloat((int) lit)
|
||||
+ " double:" + Double.longBitsToDouble(lit);
|
||||
if (fallback && Math.abs(lit) > 100) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(n).append("(0x").append(Long.toHexString(lit));
|
||||
if (type == null || type.contains(PrimitiveType.FLOAT)) {
|
||||
sb.append(", float:").append(Float.intBitsToFloat((int) lit));
|
||||
}
|
||||
if (type == null || type.contains(PrimitiveType.DOUBLE)) {
|
||||
sb.append(", double:").append(Double.longBitsToDouble(lit));
|
||||
}
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -46,15 +70,15 @@ public class TypeGen {
|
||||
case BOOLEAN:
|
||||
return lit == 0 ? "false" : "true";
|
||||
case CHAR:
|
||||
return StringUtils.unescapeChar((char) lit);
|
||||
return stringUtils.unescapeChar((char) lit, cast);
|
||||
case BYTE:
|
||||
return formatByte((byte) lit);
|
||||
return formatByte(lit, cast);
|
||||
case SHORT:
|
||||
return formatShort((short) lit);
|
||||
return formatShort(lit, cast);
|
||||
case INT:
|
||||
return formatInteger((int) lit);
|
||||
return formatInteger(lit, cast);
|
||||
case LONG:
|
||||
return formatLong(lit);
|
||||
return formatLong(lit, cast);
|
||||
case FLOAT:
|
||||
return formatFloat(Float.intBitsToFloat((int) lit));
|
||||
case DOUBLE:
|
||||
@@ -73,37 +97,40 @@ public class TypeGen {
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatShort(short s) {
|
||||
if (s == Short.MAX_VALUE) {
|
||||
public static String formatShort(long l, boolean cast) {
|
||||
if (l == Short.MAX_VALUE) {
|
||||
return "Short.MAX_VALUE";
|
||||
}
|
||||
if (s == Short.MIN_VALUE) {
|
||||
if (l == Short.MIN_VALUE) {
|
||||
return "Short.MIN_VALUE";
|
||||
}
|
||||
return "(short) " + Short.toString(s);
|
||||
String str = Long.toString(l);
|
||||
return cast ? "(short) " + str : str;
|
||||
}
|
||||
|
||||
public static String formatByte(byte b) {
|
||||
if (b == Byte.MAX_VALUE) {
|
||||
public static String formatByte(long l, boolean cast) {
|
||||
if (l == Byte.MAX_VALUE) {
|
||||
return "Byte.MAX_VALUE";
|
||||
}
|
||||
if (b == Byte.MIN_VALUE) {
|
||||
if (l == Byte.MIN_VALUE) {
|
||||
return "Byte.MIN_VALUE";
|
||||
}
|
||||
return "(byte) " + Byte.toString(b);
|
||||
String str = Long.toString(l);
|
||||
return cast ? "(byte) " + str : str;
|
||||
}
|
||||
|
||||
public static String formatInteger(int i) {
|
||||
if (i == Integer.MAX_VALUE) {
|
||||
public static String formatInteger(long l, boolean cast) {
|
||||
if (l == Integer.MAX_VALUE) {
|
||||
return "Integer.MAX_VALUE";
|
||||
}
|
||||
if (i == Integer.MIN_VALUE) {
|
||||
if (l == Integer.MIN_VALUE) {
|
||||
return "Integer.MIN_VALUE";
|
||||
}
|
||||
return Integer.toString(i);
|
||||
String str = Long.toString(l);
|
||||
return cast ? "(int) " + str : str;
|
||||
}
|
||||
|
||||
public static String formatLong(long l) {
|
||||
public static String formatLong(long l, boolean cast) {
|
||||
if (l == Long.MAX_VALUE) {
|
||||
return "Long.MAX_VALUE";
|
||||
}
|
||||
@@ -111,8 +138,8 @@ public class TypeGen {
|
||||
return "Long.MIN_VALUE";
|
||||
}
|
||||
String str = Long.toString(l);
|
||||
if (Math.abs(l) >= Integer.MAX_VALUE) {
|
||||
str += "L";
|
||||
if (cast || Math.abs(l) >= Integer.MAX_VALUE) {
|
||||
return str + 'L';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
@@ -136,7 +163,7 @@ public class TypeGen {
|
||||
if (d == Double.MIN_NORMAL) {
|
||||
return "Double.MIN_NORMAL";
|
||||
}
|
||||
return Double.toString(d) + "d";
|
||||
return Double.toString(d) + 'd';
|
||||
}
|
||||
|
||||
public static String formatFloat(float f) {
|
||||
@@ -158,7 +185,6 @@ public class TypeGen {
|
||||
if (f == Float.MIN_NORMAL) {
|
||||
return "Float.MIN_NORMAL";
|
||||
}
|
||||
return Float.toString(f) + "f";
|
||||
return Float.toString(f) + 'f';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
package jadx.core.codegen.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.ClassGen;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.codegen.MethodGen;
|
||||
import jadx.core.codegen.json.cls.JsonClass;
|
||||
import jadx.core.codegen.json.cls.JsonCodeLine;
|
||||
import jadx.core.codegen.json.cls.JsonField;
|
||||
import jadx.core.codegen.json.cls.JsonMethod;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class JsonCodeGen {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES)
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
|
||||
private final ClassNode cls;
|
||||
private final JadxArgs args;
|
||||
private final RootNode root;
|
||||
|
||||
public JsonCodeGen(ClassNode cls) {
|
||||
this.cls = cls;
|
||||
this.root = cls.root();
|
||||
this.args = root.getArgs();
|
||||
}
|
||||
|
||||
public String process() {
|
||||
JsonClass jsonCls = processCls(cls, null);
|
||||
return GSON.toJson(jsonCls);
|
||||
}
|
||||
|
||||
private JsonClass processCls(ClassNode cls, @Nullable ClassGen parentCodeGen) {
|
||||
ClassGen classGen;
|
||||
if (parentCodeGen == null) {
|
||||
classGen = new ClassGen(cls, args);
|
||||
} else {
|
||||
classGen = new ClassGen(cls, parentCodeGen);
|
||||
}
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
|
||||
JsonClass jsonCls = new JsonClass();
|
||||
jsonCls.setPkg(classInfo.getAliasPkg());
|
||||
jsonCls.setDex(cls.getInputFileName());
|
||||
jsonCls.setName(classInfo.getFullName());
|
||||
if (classInfo.hasAlias()) {
|
||||
jsonCls.setAlias(classInfo.getAliasFullName());
|
||||
}
|
||||
jsonCls.setType(getClassTypeStr(cls));
|
||||
jsonCls.setAccessFlags(cls.getAccessFlags().rawValue());
|
||||
if (!Objects.equals(cls.getSuperClass(), ArgType.OBJECT)) {
|
||||
jsonCls.setSuperClass(getTypeAlias(cls.getSuperClass()));
|
||||
}
|
||||
if (!cls.getInterfaces().isEmpty()) {
|
||||
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), this::getTypeAlias));
|
||||
}
|
||||
|
||||
CodeWriter cw = new CodeWriter();
|
||||
CodeGenUtils.addComments(cw, cls);
|
||||
classGen.insertDecompilationProblems(cw, cls);
|
||||
classGen.addClassDeclaration(cw);
|
||||
jsonCls.setDeclaration(cw.getCodeStr());
|
||||
|
||||
addFields(cls, jsonCls, classGen);
|
||||
addMethods(cls, jsonCls, classGen);
|
||||
addInnerClasses(cls, jsonCls, classGen);
|
||||
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
List<String> imports = Utils.collectionMap(classGen.getImports(), ClassInfo::getAliasFullName);
|
||||
Collections.sort(imports);
|
||||
jsonCls.setImports(imports);
|
||||
}
|
||||
return jsonCls;
|
||||
}
|
||||
|
||||
private void addInnerClasses(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
|
||||
List<ClassNode> innerClasses = cls.getInnerClasses();
|
||||
if (innerClasses.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
jsonCls.setInnerClasses(new ArrayList<>(innerClasses.size()));
|
||||
for (ClassNode innerCls : innerClasses) {
|
||||
if (innerCls.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
JsonClass innerJsonCls = processCls(innerCls, classGen);
|
||||
jsonCls.getInnerClasses().add(innerJsonCls);
|
||||
}
|
||||
}
|
||||
|
||||
private void addFields(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
|
||||
jsonCls.setFields(new ArrayList<>());
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
JsonField jsonField = new JsonField();
|
||||
jsonField.setName(field.getName());
|
||||
if (field.getFieldInfo().hasAlias()) {
|
||||
jsonField.setAlias(field.getAlias());
|
||||
}
|
||||
|
||||
CodeWriter cw = new CodeWriter();
|
||||
classGen.addField(cw, field);
|
||||
jsonField.setDeclaration(cw.getCodeStr());
|
||||
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
|
||||
|
||||
jsonCls.getFields().add(jsonField);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMethods(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
|
||||
jsonCls.setMethods(new ArrayList<>());
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
JsonMethod jsonMth = new JsonMethod();
|
||||
jsonMth.setName(mth.getName());
|
||||
if (mth.getMethodInfo().hasAlias()) {
|
||||
jsonMth.setAlias(mth.getAlias());
|
||||
}
|
||||
jsonMth.setSignature(mth.getMethodInfo().getShortId());
|
||||
jsonMth.setReturnType(getTypeAlias(mth.getReturnType()));
|
||||
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), this::getTypeAlias));
|
||||
|
||||
MethodGen mthGen = new MethodGen(classGen, mth);
|
||||
CodeWriter cw = new CodeWriter();
|
||||
mthGen.addDefinition(cw);
|
||||
jsonMth.setDeclaration(cw.getCodeStr());
|
||||
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
|
||||
jsonMth.setLines(fillMthCode(mth, mthGen));
|
||||
jsonMth.setOffset("0x" + Long.toHexString(mth.getMethodCodeOffset()));
|
||||
jsonCls.getMethods().add(jsonMth);
|
||||
}
|
||||
}
|
||||
|
||||
private List<JsonCodeLine> fillMthCode(MethodNode mth, MethodGen mthGen) {
|
||||
if (mth.isNoCode()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
CodeWriter cw = new CodeWriter();
|
||||
try {
|
||||
mthGen.addInstructions(cw);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Method generation error", e);
|
||||
}
|
||||
ICodeInfo code = cw.finish();
|
||||
String codeStr = code.getCodeStr();
|
||||
if (codeStr.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String[] lines = codeStr.split(CodeWriter.NL);
|
||||
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
||||
Map<CodePosition, Object> annotations = code.getAnnotations();
|
||||
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
||||
|
||||
int linesCount = lines.length;
|
||||
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
String codeLine = lines[i];
|
||||
int line = i + 2;
|
||||
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
||||
jsonCodeLine.setCode(codeLine);
|
||||
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
||||
Object obj = annotations.get(new CodePosition(line, 0));
|
||||
if (obj instanceof InsnNode) {
|
||||
long offset = ((InsnNode) obj).getOffset();
|
||||
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
||||
}
|
||||
codeLines.add(jsonCodeLine);
|
||||
}
|
||||
return codeLines;
|
||||
}
|
||||
|
||||
private String getTypeAlias(ArgType clsType) {
|
||||
if (Objects.equals(clsType, ArgType.OBJECT)) {
|
||||
return ArgType.OBJECT.getObject();
|
||||
}
|
||||
if (clsType.isObject()) {
|
||||
ClassInfo classInfo = ClassInfo.fromType(root, clsType);
|
||||
return classInfo.getAliasFullName();
|
||||
}
|
||||
return clsType.toString();
|
||||
}
|
||||
|
||||
private String getClassTypeStr(ClassNode cls) {
|
||||
if (cls.isEnum()) {
|
||||
return "enum";
|
||||
}
|
||||
if (cls.getAccessFlags().isInterface()) {
|
||||
return "interface";
|
||||
}
|
||||
return "class";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package jadx.core.codegen.json;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.json.mapping.JsonClsMapping;
|
||||
import jadx.core.codegen.json.mapping.JsonFieldMapping;
|
||||
import jadx.core.codegen.json.mapping.JsonMapping;
|
||||
import jadx.core.codegen.json.mapping.JsonMthMapping;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JsonMappingGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JsonMappingGen.class);
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES)
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
|
||||
public static void dump(RootNode root) {
|
||||
JsonMapping mapping = new JsonMapping();
|
||||
fillMapping(mapping, root);
|
||||
|
||||
JadxArgs args = root.getArgs();
|
||||
File outDirSrc = args.getOutDirSrc().getAbsoluteFile();
|
||||
File mappingFile = new File(outDirSrc, "mapping.json");
|
||||
FileUtils.makeDirsForFile(mappingFile);
|
||||
try (Writer writer = new FileWriter(mappingFile)) {
|
||||
GSON.toJson(mapping, writer);
|
||||
LOG.info("Save mappings to {}", mappingFile.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to save mapping json", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillMapping(JsonMapping mapping, RootNode root) {
|
||||
List<ClassNode> classes = root.getClasses(true);
|
||||
mapping.setClasses(new ArrayList<>(classes.size()));
|
||||
for (ClassNode cls : classes) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
JsonClsMapping jsonCls = new JsonClsMapping();
|
||||
jsonCls.setName(classInfo.getRawName());
|
||||
jsonCls.setAlias(classInfo.getAliasFullName());
|
||||
jsonCls.setInner(classInfo.isInner());
|
||||
jsonCls.setJson(cls.getTopParentClass().getClassInfo().getAliasFullPath() + ".json");
|
||||
if (classInfo.isInner()) {
|
||||
jsonCls.setTopClass(cls.getTopParentClass().getClassInfo().getFullName());
|
||||
}
|
||||
addFields(cls, jsonCls);
|
||||
addMethods(cls, jsonCls);
|
||||
mapping.getClasses().add(jsonCls);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addMethods(ClassNode cls, JsonClsMapping jsonCls) {
|
||||
List<MethodNode> methods = cls.getMethods();
|
||||
if (methods.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
jsonCls.setMethods(new ArrayList<>(methods.size()));
|
||||
for (MethodNode method : methods) {
|
||||
JsonMthMapping jsonMethod = new JsonMthMapping();
|
||||
MethodInfo methodInfo = method.getMethodInfo();
|
||||
jsonMethod.setSignature(methodInfo.getShortId());
|
||||
jsonMethod.setName(methodInfo.getName());
|
||||
jsonMethod.setAlias(methodInfo.getAlias());
|
||||
jsonMethod.setOffset("0x" + Long.toHexString(method.getMethodCodeOffset()));
|
||||
jsonCls.getMethods().add(jsonMethod);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFields(ClassNode cls, JsonClsMapping jsonCls) {
|
||||
List<FieldNode> fields = cls.getFields();
|
||||
if (fields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
jsonCls.setFields(new ArrayList<>(fields.size()));
|
||||
for (FieldNode field : fields) {
|
||||
JsonFieldMapping jsonField = new JsonFieldMapping();
|
||||
jsonField.setName(field.getName());
|
||||
jsonField.setAlias(field.getAlias());
|
||||
jsonCls.getFields().add(jsonField);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonMappingGen() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class JsonClass extends JsonNode {
|
||||
@SerializedName("package")
|
||||
private String pkg;
|
||||
private String type; // class, interface, enum
|
||||
@SerializedName("extends")
|
||||
private String superClass;
|
||||
@SerializedName("implements")
|
||||
private List<String> interfaces;
|
||||
private String dex;
|
||||
|
||||
private List<JsonField> fields;
|
||||
private List<JsonMethod> methods;
|
||||
private List<JsonClass> innerClasses;
|
||||
|
||||
private List<String> imports;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getSuperClass() {
|
||||
return superClass;
|
||||
}
|
||||
|
||||
public void setSuperClass(String superClass) {
|
||||
this.superClass = superClass;
|
||||
}
|
||||
|
||||
public List<String> getInterfaces() {
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
public void setInterfaces(List<String> interfaces) {
|
||||
this.interfaces = interfaces;
|
||||
}
|
||||
|
||||
public List<JsonField> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(List<JsonField> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public List<JsonMethod> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
public void setMethods(List<JsonMethod> methods) {
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public List<JsonClass> getInnerClasses() {
|
||||
return innerClasses;
|
||||
}
|
||||
|
||||
public void setInnerClasses(List<JsonClass> innerClasses) {
|
||||
this.innerClasses = innerClasses;
|
||||
}
|
||||
|
||||
public String getPkg() {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public void setPkg(String pkg) {
|
||||
this.pkg = pkg;
|
||||
}
|
||||
|
||||
public String getDex() {
|
||||
return dex;
|
||||
}
|
||||
|
||||
public void setDex(String dex) {
|
||||
this.dex = dex;
|
||||
}
|
||||
|
||||
public List<String> getImports() {
|
||||
return imports;
|
||||
}
|
||||
|
||||
public void setImports(List<String> imports) {
|
||||
this.imports = imports;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class JsonCodeLine {
|
||||
private String code;
|
||||
private String offset;
|
||||
private Integer sourceLine;
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(String offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public Integer getSourceLine() {
|
||||
return sourceLine;
|
||||
}
|
||||
|
||||
public void setSourceLine(@Nullable Integer sourceLine) {
|
||||
this.sourceLine = sourceLine;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
public class JsonField extends JsonNode {
|
||||
String type;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JsonMethod extends JsonNode {
|
||||
private String signature;
|
||||
private String returnType;
|
||||
private List<String> arguments;
|
||||
private List<JsonCodeLine> lines;
|
||||
private String offset;
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public void setReturnType(String returnType) {
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
public List<String> getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public void setArguments(List<String> arguments) {
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
public List<JsonCodeLine> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void setLines(List<JsonCodeLine> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
public String getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(String offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
public class JsonNode {
|
||||
private String name;
|
||||
private String alias;
|
||||
private String declaration;
|
||||
private int accessFlags;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getDeclaration() {
|
||||
return declaration;
|
||||
}
|
||||
|
||||
public void setDeclaration(String declaration) {
|
||||
this.declaration = declaration;
|
||||
}
|
||||
|
||||
public int getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
public void setAccessFlags(int accessFlags) {
|
||||
this.accessFlags = accessFlags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package jadx.core.codegen.json.mapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JsonClsMapping {
|
||||
private String name;
|
||||
private String alias;
|
||||
|
||||
private String json;
|
||||
private boolean inner;
|
||||
private String topClass;
|
||||
|
||||
private List<JsonFieldMapping> fields;
|
||||
private List<JsonMthMapping> methods;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
|
||||
public void setJson(String json) {
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
public boolean isInner() {
|
||||
return inner;
|
||||
}
|
||||
|
||||
public void setInner(boolean inner) {
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
public String getTopClass() {
|
||||
return topClass;
|
||||
}
|
||||
|
||||
public void setTopClass(String topClass) {
|
||||
this.topClass = topClass;
|
||||
}
|
||||
|
||||
public List<JsonFieldMapping> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(List<JsonFieldMapping> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public List<JsonMthMapping> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
public void setMethods(List<JsonMthMapping> methods) {
|
||||
this.methods = methods;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.core.codegen.json.mapping;
|
||||
|
||||
public class JsonFieldMapping {
|
||||
private String name;
|
||||
private String alias;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package jadx.core.codegen.json.mapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JsonMapping {
|
||||
private List<JsonClsMapping> classes;
|
||||
|
||||
public List<JsonClsMapping> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public void setClasses(List<JsonClsMapping> classes) {
|
||||
this.classes = classes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package jadx.core.codegen.json.mapping;
|
||||
|
||||
public class JsonMthMapping {
|
||||
private String signature;
|
||||
private String name;
|
||||
private String alias;
|
||||
private String offset;
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(String offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,37 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
class DeobfPresets {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
|
||||
|
||||
private static final String MAP_FILE_CHARSET = "UTF-8";
|
||||
private static final Charset MAP_FILE_CHARSET = UTF_8;
|
||||
|
||||
private final Deobfuscator deobfuscator;
|
||||
private final File deobfMapFile;
|
||||
private final Path deobfMapFile;
|
||||
|
||||
private final Map<String, String> clsPresetMap = new HashMap<String, String>();
|
||||
private final Map<String, String> fldPresetMap = new HashMap<String, String>();
|
||||
private final Map<String, String> mthPresetMap = new HashMap<String, String>();
|
||||
private final Map<String, String> clsPresetMap = new HashMap<>();
|
||||
private final Map<String, String> fldPresetMap = new HashMap<>();
|
||||
private final Map<String, String> mthPresetMap = new HashMap<>();
|
||||
|
||||
public DeobfPresets(Deobfuscator deobfuscator, File deobfMapFile) {
|
||||
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) {
|
||||
this.deobfuscator = deobfuscator;
|
||||
this.deobfMapFile = deobfMapFile;
|
||||
}
|
||||
@@ -37,12 +40,12 @@ class DeobfPresets {
|
||||
* Loads deobfuscator presets
|
||||
*/
|
||||
public void load() {
|
||||
if (!deobfMapFile.exists()) {
|
||||
if (!Files.exists(deobfMapFile)) {
|
||||
return;
|
||||
}
|
||||
LOG.info("Loading obfuscation map from: {}", deobfMapFile.getAbsoluteFile());
|
||||
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
|
||||
try {
|
||||
List<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET);
|
||||
List<String> lines = Files.readAllLines(deobfMapFile, MAP_FILE_CHARSET);
|
||||
for (String l : lines) {
|
||||
l = l.trim();
|
||||
if (l.isEmpty() || l.startsWith("#")) {
|
||||
@@ -65,7 +68,7 @@ class DeobfPresets {
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,18 +82,18 @@ class DeobfPresets {
|
||||
|
||||
public void save(boolean forceSave) {
|
||||
try {
|
||||
if (deobfMapFile.exists()) {
|
||||
if (Files.exists(deobfMapFile)) {
|
||||
if (forceSave) {
|
||||
dumpMapping();
|
||||
} else {
|
||||
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||
deobfMapFile.getAbsolutePath());
|
||||
deobfMapFile.toAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
dumpMapping();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +101,7 @@ class DeobfPresets {
|
||||
* Saves DefaultDeobfuscator presets
|
||||
*/
|
||||
private void dumpMapping() throws IOException {
|
||||
List<String> list = new ArrayList<String>();
|
||||
List<String> list = new ArrayList<>();
|
||||
// packages
|
||||
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
|
||||
for (PackageNode pp : p.getInnerPackages()) {
|
||||
@@ -112,18 +115,20 @@ class DeobfPresets {
|
||||
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
|
||||
if (deobfClsInfo.getAlias() != null) {
|
||||
list.add(String.format("c %s = %s",
|
||||
deobfClsInfo.getCls().getClassInfo().getFullName(), deobfClsInfo.getAlias()));
|
||||
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
|
||||
}
|
||||
}
|
||||
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
|
||||
list.add(String.format("f %s = %s", fld.getFullId(), fld.getAlias()));
|
||||
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
|
||||
}
|
||||
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
|
||||
list.add(String.format("m %s = %s", mth.getFullId(), mth.getAlias()));
|
||||
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
|
||||
}
|
||||
Collections.sort(list);
|
||||
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
|
||||
list.clear();
|
||||
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
|
||||
@@ -136,15 +141,15 @@ class DeobfPresets {
|
||||
}
|
||||
|
||||
public String getForCls(ClassInfo cls) {
|
||||
return clsPresetMap.get(cls.getFullName());
|
||||
return clsPresetMap.get(cls.makeRawFullName());
|
||||
}
|
||||
|
||||
public String getForFld(FieldInfo fld) {
|
||||
return fldPresetMap.get(fld.getFullId());
|
||||
return fldPresetMap.get(fld.getRawFullId());
|
||||
}
|
||||
|
||||
public String getForMth(MethodInfo mth) {
|
||||
return mthPresetMap.get(mth.getFullId());
|
||||
return mthPresetMap.get(mth.getRawFullId());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
||||
@@ -1,28 +1,35 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.kotlin.KotlinMetadataUtils;
|
||||
|
||||
public class Deobfuscator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
||||
|
||||
@@ -31,31 +38,39 @@ public class Deobfuscator {
|
||||
public static final String CLASS_NAME_SEPARATOR = ".";
|
||||
public static final String INNER_CLASS_SEPARATOR = "$";
|
||||
|
||||
private final IJadxArgs args;
|
||||
@NotNull
|
||||
private final List<DexNode> dexNodes;
|
||||
private final JadxArgs args;
|
||||
private final RootNode root;
|
||||
private final DeobfPresets deobfPresets;
|
||||
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
|
||||
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
|
||||
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
|
||||
private final Map<FieldInfo, String> fldMap = new HashMap<>();
|
||||
private final Map<MethodInfo, String> mthMap = new HashMap<>();
|
||||
|
||||
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<>();
|
||||
private final List<OverridedMethodsNode> ovrd = new ArrayList<>();
|
||||
|
||||
private final PackageNode rootPackage = new PackageNode("");
|
||||
private final Set<String> pkgSet = new TreeSet<String>();
|
||||
private final Set<String> pkgSet = new TreeSet<>();
|
||||
private final Set<String> reservedClsNames = new HashSet<>();
|
||||
|
||||
private final int maxLength;
|
||||
private final int minLength;
|
||||
private final boolean useSourceNameAsAlias;
|
||||
private final boolean parseKotlinMetadata;
|
||||
|
||||
private int pkgIndex = 0;
|
||||
private int clsIndex = 0;
|
||||
private int fldIndex = 0;
|
||||
private int mthIndex = 0;
|
||||
|
||||
public Deobfuscator(IJadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
|
||||
public Deobfuscator(JadxArgs args, RootNode root, Path deobfMapFile) {
|
||||
this.args = args;
|
||||
this.dexNodes = dexNodes;
|
||||
this.root = root;
|
||||
|
||||
this.minLength = args.getDeobfuscationMinLength();
|
||||
this.maxLength = args.getDeobfuscationMaxLength();
|
||||
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
|
||||
this.parseKotlinMetadata = args.isParseKotlinMetadata();
|
||||
|
||||
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
||||
}
|
||||
@@ -66,8 +81,20 @@ public class Deobfuscator {
|
||||
initIndexes();
|
||||
}
|
||||
process();
|
||||
}
|
||||
|
||||
public void savePresets() {
|
||||
deobfPresets.save(args.isDeobfuscationForceSave());
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
deobfPresets.clear();
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
private void initIndexes() {
|
||||
@@ -78,10 +105,11 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private void preProcess() {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
doClass(cls);
|
||||
}
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
|
||||
}
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
preProcessClass(cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,39 +118,164 @@ public class Deobfuscator {
|
||||
if (DEBUG) {
|
||||
dumpAlias();
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
processClass(dexNode, cls);
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
processClass(cls);
|
||||
}
|
||||
postProcess();
|
||||
}
|
||||
|
||||
private void postProcess() {
|
||||
int id = 1;
|
||||
for (OverridedMethodsNode o : ovrd) {
|
||||
boolean aliasFromPreset = false;
|
||||
String aliasToUse = null;
|
||||
for (MethodInfo mth : o.getMethods()) {
|
||||
if (mth.isAliasFromPreset()) {
|
||||
aliasToUse = mth.getAlias();
|
||||
aliasFromPreset = true;
|
||||
}
|
||||
}
|
||||
for (MethodInfo mth : o.getMethods()) {
|
||||
if (aliasToUse == null) {
|
||||
if (mth.hasAlias() && !mth.isAliasFromPreset()) {
|
||||
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
|
||||
}
|
||||
aliasToUse = mth.getAlias();
|
||||
}
|
||||
mth.setAlias(aliasToUse);
|
||||
mth.setAliasFromPreset(aliasFromPreset);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveOverriding(MethodNode mth) {
|
||||
Set<ClassNode> clsParents = new LinkedHashSet<>();
|
||||
collectClassHierarchy(mth.getParentClass(), clsParents);
|
||||
|
||||
String mthSignature = mth.getMethodInfo().makeSignature(false);
|
||||
Set<MethodInfo> overrideSet = new LinkedHashSet<>();
|
||||
for (ClassNode classNode : clsParents) {
|
||||
MethodInfo methodInfo = getMthOverride(classNode.getMethods(), mthSignature);
|
||||
if (methodInfo != null) {
|
||||
overrideSet.add(methodInfo);
|
||||
}
|
||||
}
|
||||
if (overrideSet.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
OverridedMethodsNode overrideNode = getOverrideMethodsNode(overrideSet);
|
||||
if (overrideNode == null) {
|
||||
overrideNode = new OverridedMethodsNode(overrideSet);
|
||||
ovrd.add(overrideNode);
|
||||
}
|
||||
for (MethodInfo overrideMth : overrideSet) {
|
||||
if (!ovrdMap.containsKey(overrideMth)) {
|
||||
ovrdMap.put(overrideMth, overrideNode);
|
||||
overrideNode.add(overrideMth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
deobfPresets.clear();
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
private OverridedMethodsNode getOverrideMethodsNode(Set<MethodInfo> overrideSet) {
|
||||
for (MethodInfo overrideMth : overrideSet) {
|
||||
OverridedMethodsNode node = ovrdMap.get(overrideMth);
|
||||
if (node != null) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void processClass(DexNode dex, ClassNode cls) {
|
||||
private MethodInfo getMthOverride(List<MethodNode> methods, String mthSignature) {
|
||||
for (MethodNode m : methods) {
|
||||
MethodInfo mthInfo = m.getMethodInfo();
|
||||
if (mthInfo.getShortId().startsWith(mthSignature)) {
|
||||
return mthInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void collectClassHierarchy(ClassNode cls, Set<ClassNode> collected) {
|
||||
boolean added = collected.add(cls);
|
||||
if (added) {
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
ClassNode superNode = cls.root().resolveClass(superClass);
|
||||
if (superNode != null) {
|
||||
collectClassHierarchy(superNode, collected);
|
||||
}
|
||||
}
|
||||
|
||||
for (ArgType argType : cls.getInterfaces()) {
|
||||
ClassNode interfaceNode = cls.root().resolveClass(argType);
|
||||
if (interfaceNode != null) {
|
||||
collectClassHierarchy(interfaceNode, collected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(ClassNode cls) {
|
||||
if (isR(cls.getParentClass())) {
|
||||
return;
|
||||
}
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
String fullName = getClassFullName(clsInfo);
|
||||
if (!fullName.equals(clsInfo.getFullName())) {
|
||||
clsInfo.rename(dex, fullName);
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
clsInfo.changeShortName(deobfClsInfo.getAlias());
|
||||
PackageNode pkgNode = deobfClsInfo.getPkg();
|
||||
if (!clsInfo.isInner() && pkgNode.hasAnyAlias()) {
|
||||
clsInfo.changePkg(pkgNode.getFullAlias());
|
||||
}
|
||||
} else if (!clsInfo.isInner()) {
|
||||
// check if package renamed
|
||||
PackageNode pkgNode = getPackageNode(clsInfo.getPackage(), false);
|
||||
if (pkgNode != null && pkgNode.hasAnyAlias()) {
|
||||
clsInfo.changePkg(pkgNode.getFullAlias());
|
||||
}
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = getFieldAlias(field);
|
||||
if (alias != null) {
|
||||
fieldInfo.setAlias(alias);
|
||||
if (field.contains(AFlag.DONT_RENAME)) {
|
||||
continue;
|
||||
}
|
||||
renameField(field);
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
MethodInfo methodInfo = mth.getMethodInfo();
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
methodInfo.setAlias(alias);
|
||||
}
|
||||
renameMethod(mth);
|
||||
}
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
processClass(innerCls);
|
||||
}
|
||||
}
|
||||
|
||||
private void renameField(FieldNode field) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = getFieldAlias(field);
|
||||
if (alias != null) {
|
||||
fieldInfo.setAlias(alias);
|
||||
}
|
||||
}
|
||||
|
||||
public void forceRenameField(FieldNode field) {
|
||||
field.getFieldInfo().setAlias(makeFieldAlias(field));
|
||||
}
|
||||
|
||||
private void renameMethod(MethodNode mth) {
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
mth.getMethodInfo().setAlias(alias);
|
||||
}
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(mth);
|
||||
}
|
||||
}
|
||||
|
||||
public void forceRenameMethod(MethodNode mth) {
|
||||
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(mth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +289,8 @@ public class Deobfuscator {
|
||||
*
|
||||
* @param fullPkgName full package name
|
||||
* @param create if {@code true} then will create all absent objects
|
||||
* @return package node object or {@code null} if no package found and <b>create</b> set to {@code false}
|
||||
* @return package node object or {@code null} if no package found and <b>create</b> set to
|
||||
* {@code false}
|
||||
*/
|
||||
private PackageNode getPackageNode(String fullPkgName, boolean create) {
|
||||
if (fullPkgName.isEmpty() || fullPkgName.equals(CLASS_NAME_SEPARATOR)) {
|
||||
@@ -183,22 +337,24 @@ public class Deobfuscator {
|
||||
return prefix + clsInfo.getShortName();
|
||||
}
|
||||
|
||||
private void doClass(ClassNode cls) {
|
||||
private void preProcessClass(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String pkgFullName = classInfo.getPackage();
|
||||
PackageNode pkg = getPackageNode(pkgFullName, true);
|
||||
doPkg(pkg, pkgFullName);
|
||||
processPackageFull(pkg, pkgFullName);
|
||||
|
||||
String alias = deobfPresets.getForCls(classInfo);
|
||||
if (alias != null) {
|
||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||
return;
|
||||
} else {
|
||||
if (!clsMap.containsKey(classInfo)) {
|
||||
String clsShortName = classInfo.getShortName();
|
||||
boolean badName = shouldRename(clsShortName) || reservedClsNames.contains(clsShortName);
|
||||
makeClsAlias(cls, badName);
|
||||
}
|
||||
}
|
||||
if (clsMap.containsKey(classInfo)) {
|
||||
return;
|
||||
}
|
||||
if (shouldRename(classInfo.getShortName())) {
|
||||
makeClsAlias(cls);
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
preProcessClass(innerCls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,42 +363,148 @@ public class Deobfuscator {
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getAlias();
|
||||
}
|
||||
return makeClsAlias(cls);
|
||||
return makeClsAlias(cls, true);
|
||||
}
|
||||
|
||||
private String makeClsAlias(ClassNode cls) {
|
||||
public String getPkgAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String alias = getAliasFromSourceFile(cls);
|
||||
if (alias == null) {
|
||||
String clsName = classInfo.getShortName();
|
||||
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
|
||||
PackageNode pkg;
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
pkg = deobfClsInfo.getPkg();
|
||||
} else {
|
||||
String fullPkgName = classInfo.getPackage();
|
||||
pkg = getPackageNode(fullPkgName, true);
|
||||
processPackageFull(pkg, fullPkgName);
|
||||
}
|
||||
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
|
||||
if (pkg.hasAnyAlias()) {
|
||||
return pkg.getFullAlias();
|
||||
} else {
|
||||
return pkg.getFullName();
|
||||
}
|
||||
}
|
||||
|
||||
private String makeClsAlias(ClassNode cls, boolean badName) {
|
||||
String alias = null;
|
||||
String pkgName = null;
|
||||
if (this.parseKotlinMetadata) {
|
||||
ClassInfo kotlinCls = KotlinMetadataUtils.getClassName(cls);
|
||||
if (kotlinCls != null) {
|
||||
alias = prepareNameFull(kotlinCls.getShortName(), "C");
|
||||
pkgName = kotlinCls.getPackage();
|
||||
}
|
||||
}
|
||||
if (alias == null && this.useSourceNameAsAlias) {
|
||||
alias = getAliasFromSourceFile(cls);
|
||||
}
|
||||
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (alias == null) {
|
||||
if (badName) {
|
||||
String clsName = classInfo.getShortName();
|
||||
String prefix = makeClsPrefix(cls);
|
||||
alias = String.format("%sC%04d%s", prefix, clsIndex++, prepareNamePart(clsName));
|
||||
} else {
|
||||
// rename not needed
|
||||
return classInfo.getShortName();
|
||||
}
|
||||
}
|
||||
if (pkgName == null) {
|
||||
pkgName = classInfo.getPackage();
|
||||
}
|
||||
PackageNode pkg = getPackageNode(pkgName, true);
|
||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a prefix for a class name that bases on certain class properties, certain
|
||||
* extended superclasses or implemented interfaces.
|
||||
*/
|
||||
private String makeClsPrefix(ClassNode cls) {
|
||||
if (cls.isEnum()) {
|
||||
return "Enum";
|
||||
}
|
||||
String result = "";
|
||||
if (cls.getAccessFlags().isAbstract()) {
|
||||
result += "Abstract";
|
||||
}
|
||||
|
||||
// Process current class and all super classes
|
||||
ClassNode currentCls = cls;
|
||||
outerLoop: while (currentCls != null) {
|
||||
if (currentCls.getSuperClass() != null) {
|
||||
String superClsName = currentCls.getSuperClass().getObject();
|
||||
if (superClsName.startsWith("android.app.")) {
|
||||
// e.g. Activity or Fragment
|
||||
result += superClsName.substring(12);
|
||||
break;
|
||||
} else if (superClsName.startsWith("android.os.")) {
|
||||
// e.g. AsyncTask
|
||||
result += superClsName.substring(11);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (ArgType intf : cls.getInterfaces()) {
|
||||
String intfClsName = intf.getObject();
|
||||
if (intfClsName.equals("java.lang.Runnable")) {
|
||||
result += "Runnable";
|
||||
break outerLoop;
|
||||
} else if (intfClsName.startsWith("java.util.concurrent.")) {
|
||||
// e.g. Callable
|
||||
result += intfClsName.substring(21);
|
||||
break outerLoop;
|
||||
} else if (intfClsName.startsWith("android.view.")) {
|
||||
// e.g. View.OnClickListener
|
||||
result += intfClsName.substring(13);
|
||||
break outerLoop;
|
||||
} else if (intfClsName.startsWith("android.content.")) {
|
||||
// e.g. DialogInterface.OnClickListener
|
||||
result += intfClsName.substring(16);
|
||||
break outerLoop;
|
||||
}
|
||||
}
|
||||
if (currentCls.getSuperClass() == null) {
|
||||
break;
|
||||
}
|
||||
currentCls = cls.root().resolveClass(currentCls.getSuperClass());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getAliasFromSourceFile(ClassNode cls) {
|
||||
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
|
||||
if (sourceFileAttr == null) {
|
||||
return null;
|
||||
}
|
||||
if (cls.getClassInfo().isInner()) {
|
||||
return null;
|
||||
}
|
||||
String name = sourceFileAttr.getFileName();
|
||||
if (name.endsWith(".java")) {
|
||||
name = name.substring(0, name.length() - ".java".length());
|
||||
} else if (name.endsWith(".kt")) {
|
||||
name = name.substring(0, name.length() - ".kt".length());
|
||||
}
|
||||
if (NameMapper.isValidIdentifier(name)
|
||||
&& !NameMapper.isReserved(name)) {
|
||||
// TODO: check if no class with this name exists or already renamed
|
||||
cls.remove(AType.SOURCE_FILE);
|
||||
return name;
|
||||
if (!NameMapper.isValidAndPrintable(name)) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
||||
if (deobfClsInfo.getAlias().equals(name)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name);
|
||||
if (otherCls != null) {
|
||||
return null;
|
||||
}
|
||||
cls.remove(AType.SOURCE_FILE);
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFieldAlias(FieldNode field) {
|
||||
private String getFieldAlias(FieldNode field) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = fldMap.get(fieldInfo);
|
||||
if (alias != null) {
|
||||
@@ -260,8 +522,11 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMethodAlias(MethodNode mth) {
|
||||
private String getMethodAlias(MethodNode mth) {
|
||||
MethodInfo methodInfo = mth.getMethodInfo();
|
||||
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
|
||||
return null;
|
||||
}
|
||||
String alias = mthMap.get(methodInfo);
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
@@ -269,6 +534,7 @@ public class Deobfuscator {
|
||||
alias = deobfPresets.getForMth(methodInfo);
|
||||
if (alias != null) {
|
||||
mthMap.put(methodInfo, alias);
|
||||
methodInfo.setAliasFromPreset(true);
|
||||
return alias;
|
||||
}
|
||||
if (shouldRename(mth.getName())) {
|
||||
@@ -278,18 +544,18 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
public String makeFieldAlias(FieldNode field) {
|
||||
String alias = String.format("f%d%s", fldIndex++, makeName(field.getName()));
|
||||
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName()));
|
||||
fldMap.put(field.getFieldInfo(), alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
public String makeMethodAlias(MethodNode mth) {
|
||||
String alias = String.format("m%d%s", mthIndex++, makeName(mth.getName()));
|
||||
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
|
||||
mthMap.put(mth.getMethodInfo(), alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
private void doPkg(PackageNode pkg, String fullName) {
|
||||
private void processPackageFull(PackageNode pkg, String fullName) {
|
||||
if (pkgSet.contains(fullName)) {
|
||||
return;
|
||||
}
|
||||
@@ -299,47 +565,50 @@ public class Deobfuscator {
|
||||
PackageNode parentPkg = pkg.getParentPackage();
|
||||
while (!parentPkg.getName().isEmpty()) {
|
||||
if (!parentPkg.hasAlias()) {
|
||||
doPkg(parentPkg, parentPkg.getFullName());
|
||||
processPackageFull(parentPkg, parentPkg.getFullName());
|
||||
}
|
||||
parentPkg = parentPkg.getParentPackage();
|
||||
}
|
||||
|
||||
final String pkgName = pkg.getName();
|
||||
if (!pkg.hasAlias() && shouldRename(pkgName)) {
|
||||
final String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
|
||||
pkg.setAlias(pkgAlias);
|
||||
if (!pkg.hasAlias()) {
|
||||
String pkgName = pkg.getName();
|
||||
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|
||||
|| (args.isRenameValid() && !NameMapper.isValidIdentifier(pkgName))
|
||||
|| (args.isRenamePrintable() && !NameMapper.isAllCharsPrintable(pkgName))) {
|
||||
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkg.getName()));
|
||||
pkg.setAlias(pkgAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRename(String s) {
|
||||
return s.length() > maxLength
|
||||
|| s.length() < minLength
|
||||
|| NameMapper.isReserved(s)
|
||||
|| !NameMapper.isAllCharsPrintable(s);
|
||||
int len = s.length();
|
||||
return len < minLength || len > maxLength;
|
||||
}
|
||||
|
||||
private String makeName(String name) {
|
||||
private String prepareNamePart(String name) {
|
||||
if (name.length() > maxLength) {
|
||||
return "x" + Integer.toHexString(name.hashCode());
|
||||
return 'x' + Integer.toHexString(name.hashCode());
|
||||
}
|
||||
if (NameMapper.isReserved(name)) {
|
||||
return name;
|
||||
}
|
||||
if (!NameMapper.isAllCharsPrintable(name)) {
|
||||
return removeInvalidChars(name);
|
||||
}
|
||||
return name;
|
||||
return NameMapper.removeInvalidCharsMiddle(name);
|
||||
}
|
||||
|
||||
private String removeInvalidChars(String name) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
int ch = name.charAt(i);
|
||||
if (NameMapper.isPrintableChar(ch)) {
|
||||
sb.append((char) ch);
|
||||
}
|
||||
private String prepareNameFull(String name, String prefix) {
|
||||
if (name.length() > maxLength) {
|
||||
return makeHashName(name, prefix);
|
||||
}
|
||||
return sb.toString();
|
||||
String result = NameMapper.removeInvalidChars(name, prefix);
|
||||
if (result.isEmpty()) {
|
||||
return makeHashName(name, prefix);
|
||||
}
|
||||
if (NameMapper.isReserved(result)) {
|
||||
return prefix + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String makeHashName(String name, String invalidPrefix) {
|
||||
return invalidPrefix + 'x' + Integer.toHexString(name.hashCode());
|
||||
}
|
||||
|
||||
private void dumpClassAlias(ClassNode cls) {
|
||||
@@ -355,15 +624,13 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private void dumpAlias() {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
dumpClassAlias(cls);
|
||||
}
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
dumpClassAlias(cls);
|
||||
}
|
||||
}
|
||||
|
||||
private String getPackageName(String packageName) {
|
||||
final PackageNode pkg = getPackageNode(packageName, false);
|
||||
PackageNode pkg = getPackageNode(packageName, false);
|
||||
if (pkg != null) {
|
||||
return pkg.getFullAlias();
|
||||
}
|
||||
@@ -371,7 +638,7 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private String getClassName(ClassInfo clsInfo) {
|
||||
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.makeNameWithoutPkg();
|
||||
}
|
||||
@@ -379,11 +646,8 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private String getClassFullName(ClassNode cls) {
|
||||
return getClassFullName(cls.getClassInfo());
|
||||
}
|
||||
|
||||
private String getClassFullName(ClassInfo clsInfo) {
|
||||
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getFullName();
|
||||
}
|
||||
@@ -405,4 +669,27 @@ public class Deobfuscator {
|
||||
public PackageNode getRootPackage() {
|
||||
return rootPackage;
|
||||
}
|
||||
|
||||
private static boolean isR(ClassNode cls) {
|
||||
if (!cls.getClassInfo().getShortName().equals("R")) {
|
||||
return false;
|
||||
}
|
||||
if (!cls.getMethods().isEmpty() || !cls.getFields().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
for (MethodNode m : inner.getMethods()) {
|
||||
if (!m.getMethodInfo().isConstructor() && !m.getMethodInfo().isClassInit()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
ArgType type = field.getType();
|
||||
if (type != ArgType.INT && (!type.isArray() || type.getArrayElement() != ArgType.INT)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static jadx.core.utils.StringUtils.notEmpty;
|
||||
|
||||
public class NameMapper {
|
||||
|
||||
private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
|
||||
@@ -13,8 +15,8 @@ public class NameMapper {
|
||||
private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile(
|
||||
"(" + VALID_JAVA_IDENTIFIER + "\\.)*" + VALID_JAVA_IDENTIFIER);
|
||||
|
||||
private static final Set<String> RESERVED_NAMES = new HashSet<String>(
|
||||
Arrays.asList(new String[]{
|
||||
private static final Set<String> RESERVED_NAMES = new HashSet<>(
|
||||
Arrays.asList(
|
||||
"abstract",
|
||||
"assert",
|
||||
"boolean",
|
||||
@@ -67,20 +69,34 @@ public class NameMapper {
|
||||
"try",
|
||||
"void",
|
||||
"volatile",
|
||||
"while",
|
||||
})
|
||||
);
|
||||
"while"));
|
||||
|
||||
public static boolean isReserved(String str) {
|
||||
return RESERVED_NAMES.contains(str);
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifier(String str) {
|
||||
return VALID_JAVA_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
|
||||
return notEmpty(str)
|
||||
&& !isReserved(str)
|
||||
&& VALID_JAVA_IDENTIFIER.matcher(str).matches();
|
||||
}
|
||||
|
||||
public static boolean isValidFullIdentifier(String str) {
|
||||
return VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
|
||||
return notEmpty(str)
|
||||
&& !isReserved(str)
|
||||
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches();
|
||||
}
|
||||
|
||||
public static boolean isValidAndPrintable(String str) {
|
||||
return isValidIdentifier(str) && isAllCharsPrintable(str);
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifierStart(int codePoint) {
|
||||
return Character.isJavaIdentifierStart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifierPart(int codePoint) {
|
||||
return Character.isJavaIdentifierPart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isPrintableChar(int c) {
|
||||
@@ -96,4 +112,54 @@ public class NameMapper {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return modified string with removed:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>not printable chars (including unicode)
|
||||
* <li>chars not valid for java identifier part
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note: this 'middle' method must be used with prefixed string:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>can leave invalid chars for java identifier start (i.e numbers)
|
||||
* <li>result not checked for reserved words
|
||||
* </ul>
|
||||
* <p>
|
||||
*/
|
||||
public static String removeInvalidCharsMiddle(String name) {
|
||||
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
|
||||
return name;
|
||||
}
|
||||
int len = name.length();
|
||||
StringBuilder sb = new StringBuilder(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
int codePoint = name.codePointAt(i);
|
||||
if (isPrintableChar(codePoint) && isValidIdentifierPart(codePoint)) {
|
||||
sb.append((char) codePoint);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string with removed invalid chars, see {@link #removeInvalidCharsMiddle}
|
||||
* <p>
|
||||
* Prepend prefix if first char is not valid as java identifier start char.
|
||||
*/
|
||||
public static String removeInvalidChars(String name, String prefix) {
|
||||
String result = removeInvalidCharsMiddle(name);
|
||||
if (!result.isEmpty()) {
|
||||
int codePoint = result.codePointAt(0);
|
||||
if (!isValidIdentifierStart(codePoint)) {
|
||||
return prefix + result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private NameMapper() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
class OverridedMethodsNode {
|
||||
|
||||
private final Set<MethodInfo> methods;
|
||||
|
||||
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
|
||||
methods = methodsSet;
|
||||
}
|
||||
|
||||
public boolean contains(MethodInfo mth) {
|
||||
return methods.contains(mth);
|
||||
}
|
||||
|
||||
public void add(MethodInfo mth) {
|
||||
methods.add(mth);
|
||||
}
|
||||
|
||||
public Set<MethodInfo> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class PackageNode {
|
||||
|
||||
@@ -29,15 +30,18 @@ public class PackageNode {
|
||||
|
||||
public String getFullName() {
|
||||
if (cachedPackageFullName == null) {
|
||||
Stack<PackageNode> pp = getParentPackages();
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getName());
|
||||
while (pp.size() > 0) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
Deque<PackageNode> pp = getParentPackages();
|
||||
if (pp.isEmpty()) {
|
||||
cachedPackageFullName = "";
|
||||
} else {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getName());
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getName());
|
||||
}
|
||||
cachedPackageFullName = result.toString();
|
||||
}
|
||||
cachedPackageFullName = result.toString();
|
||||
}
|
||||
return cachedPackageFullName;
|
||||
}
|
||||
@@ -57,14 +61,29 @@ public class PackageNode {
|
||||
return packageAlias != null;
|
||||
}
|
||||
|
||||
public boolean hasAnyAlias() {
|
||||
if (hasAlias()) {
|
||||
return true;
|
||||
}
|
||||
if (parentPackage != this) {
|
||||
return parentPackage.hasAnyAlias();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getFullAlias() {
|
||||
if (cachedPackageFullAlias == null) {
|
||||
Stack<PackageNode> pp = getParentPackages();
|
||||
Deque<PackageNode> pp = getParentPackages();
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getAlias());
|
||||
while (pp.size() > 0) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
|
||||
if (!pp.isEmpty()) {
|
||||
result.append(pp.pop().getAlias());
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getAlias());
|
||||
}
|
||||
} else {
|
||||
result.append(this.getAlias());
|
||||
}
|
||||
cachedPackageFullAlias = result.toString();
|
||||
}
|
||||
@@ -81,7 +100,7 @@ public class PackageNode {
|
||||
|
||||
public void addInnerPackage(PackageNode pkg) {
|
||||
if (innerPackages.isEmpty()) {
|
||||
innerPackages = new ArrayList<PackageNode>();
|
||||
innerPackages = new ArrayList<>();
|
||||
}
|
||||
innerPackages.add(pkg);
|
||||
pkg.parentPackage = this;
|
||||
@@ -109,17 +128,21 @@ public class PackageNode {
|
||||
*
|
||||
* @return stack with parent packages
|
||||
*/
|
||||
private Stack<PackageNode> getParentPackages() {
|
||||
Stack<PackageNode> pp = new Stack<PackageNode>();
|
||||
private Deque<PackageNode> getParentPackages() {
|
||||
Deque<PackageNode> pp = new ArrayDeque<>();
|
||||
|
||||
PackageNode currentP = this;
|
||||
PackageNode parentP = currentP.getParentPackage();
|
||||
|
||||
while (currentP != parentP) {
|
||||
pp.push(currentP);
|
||||
currentP = parentP;
|
||||
parentP = currentP.getParentPackage();
|
||||
PackageNode currentPkg = this;
|
||||
PackageNode parentPkg = currentPkg.getParentPackage();
|
||||
while (currentPkg != parentPkg) {
|
||||
pp.push(currentPkg);
|
||||
currentPkg = parentPkg;
|
||||
parentPkg = currentPkg.getParentPackage();
|
||||
}
|
||||
return pp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return packageAlias;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
public enum AFlag {
|
||||
MTH_ENTER_BLOCK,
|
||||
TRY_ENTER,
|
||||
TRY_LEAVE,
|
||||
|
||||
@@ -12,19 +13,46 @@ public enum AFlag {
|
||||
RETURN, // block contains only return instruction
|
||||
ORIG_RETURN,
|
||||
|
||||
DECLARE_VAR,
|
||||
DONT_WRAP,
|
||||
|
||||
DONT_SHRINK,
|
||||
DONT_INLINE,
|
||||
DONT_GENERATE,
|
||||
SKIP,
|
||||
REMOVE,
|
||||
DONT_INLINE_CONST,
|
||||
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||
COMMENT_OUT, // process as usual, but comment insn in generated code
|
||||
REMOVE, // can be completely removed
|
||||
|
||||
HIDDEN, // instruction used inside other instruction but not listed in args
|
||||
|
||||
DONT_RENAME, // do not rename during deobfuscation
|
||||
ADDED_TO_REGION,
|
||||
|
||||
FINALLY_INSNS,
|
||||
|
||||
SKIP_FIRST_ARG,
|
||||
SKIP_ARG, // skip argument in invoke call
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
ANONYMOUS_CLASS,
|
||||
|
||||
THIS,
|
||||
SUPER,
|
||||
|
||||
/**
|
||||
* RegisterArg attribute for method arguments
|
||||
*/
|
||||
METHOD_ARGUMENT,
|
||||
|
||||
/**
|
||||
* Type of RegisterArg or SSAVar can't be changed
|
||||
*/
|
||||
IMMUTABLE_TYPE,
|
||||
|
||||
/**
|
||||
* Force inline instruction with inline assign
|
||||
*/
|
||||
FORCE_ASSIGN_INLINE,
|
||||
|
||||
CUSTOM_DECLARE, // variable for this register don't need declaration
|
||||
DECLARE_VAR,
|
||||
|
||||
ELSE_IF_CHAIN,
|
||||
|
||||
WRAPPED,
|
||||
@@ -32,5 +60,23 @@ public enum AFlag {
|
||||
|
||||
FALL_THROUGH,
|
||||
|
||||
VARARG_CALL,
|
||||
|
||||
/**
|
||||
* Use constants with explicit type: cast '(byte) 1' or type letter '7L'
|
||||
*/
|
||||
EXPLICIT_PRIMITIVE_TYPE,
|
||||
EXPLICIT_CAST,
|
||||
SOFT_CAST, // synthetic cast to help type inference (allow unchecked casts for generics)
|
||||
|
||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||
|
||||
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
|
||||
|
||||
// Class processing flags
|
||||
RESTART_CODEGEN, // codegen must be executed again
|
||||
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
|
||||
CLASS_DEEP_RELOAD, // perform deep class unload (reload) before process
|
||||
|
||||
DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
@@ -26,26 +38,61 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
*
|
||||
* @param <T> attribute class implementation
|
||||
*/
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
public class AType<T extends IAttribute> {
|
||||
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
|
||||
// class, method, field
|
||||
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
|
||||
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
|
||||
|
||||
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
|
||||
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
|
||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
|
||||
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
|
||||
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
|
||||
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
|
||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
|
||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
|
||||
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
|
||||
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
|
||||
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
|
||||
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
|
||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<IgnoreEdgeAttr>();
|
||||
// class, method
|
||||
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
|
||||
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
|
||||
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
|
||||
|
||||
// class
|
||||
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
|
||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
|
||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
|
||||
|
||||
// field
|
||||
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
|
||||
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
|
||||
|
||||
// method
|
||||
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
||||
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
|
||||
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
|
||||
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
|
||||
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
||||
|
||||
// region
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
|
||||
// block
|
||||
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
|
||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
|
||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
|
||||
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
||||
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
||||
|
||||
// block or insn
|
||||
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
|
||||
|
||||
// instruction
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
|
||||
public static final AType<IMethodDetails> METHOD_DETAILS = new AType<>();
|
||||
public static final AType<GenericInfoAttr> GENERIC_INFO = new AType<>();
|
||||
|
||||
// register
|
||||
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
|
||||
|
||||
public static final Set<AType<?>> SKIP_ON_UNLOAD = new HashSet<>(Arrays.asList(
|
||||
FIELD_REPLACE,
|
||||
METHOD_INLINE,
|
||||
SKIP_MTH_ARGS));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class AttrList<T> implements IAttribute {
|
||||
|
||||
private final AType<AttrList<T>> type;
|
||||
private final List<T> list = new LinkedList<T>();
|
||||
private final List<T> list = new ArrayList<>();
|
||||
|
||||
public AttrList(AType<AttrList<T>> type) {
|
||||
this.type = type;
|
||||
@@ -25,6 +26,6 @@ public class AttrList<T> implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(list);
|
||||
return Utils.listToString(list, CodeWriter.NL);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user