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
|
# IntelliJ Idea files
|
||||||
.idea/
|
.idea/
|
||||||
|
.run/
|
||||||
out/
|
out/
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
|
.attach_pid*
|
||||||
|
*.hprof
|
||||||
|
|
||||||
|
**/.DS_Store
|
||||||
|
|
||||||
bin/
|
bin/
|
||||||
target/
|
target/
|
||||||
build/
|
build/
|
||||||
|
classes/
|
||||||
idea/
|
idea/
|
||||||
.gradle/
|
.gradle/
|
||||||
gradle.properties
|
node_modules/
|
||||||
|
|
||||||
|
jadx-output/
|
||||||
*-tmp/
|
*-tmp/
|
||||||
|
**/tmp/
|
||||||
|
*.jobf
|
||||||
|
|
||||||
*.dex
|
|
||||||
*.class
|
*.class
|
||||||
*.dump
|
*.dump
|
||||||
*.log
|
*.log
|
||||||
*.cfg
|
*.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
|
language: java
|
||||||
jdk:
|
os: linux
|
||||||
- oraclejdk8
|
dist: trusty
|
||||||
- oraclejdk7
|
|
||||||
- openjdk6
|
# don't build on tag push
|
||||||
|
if: tag IS blank
|
||||||
|
|
||||||
|
git:
|
||||||
|
depth: false
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- chmod +x gradlew
|
- chmod +x gradlew
|
||||||
|
|
||||||
script:
|
# override install to skip 'gradle assemble'
|
||||||
- TERM=dumb ./gradlew clean build dist
|
install: true
|
||||||
|
|
||||||
after_success:
|
env:
|
||||||
- TERM=dumb ./gradlew jacocoTestReport coveralls
|
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:
|
script: ./gradlew clean build
|
||||||
directories:
|
|
||||||
- $HOME/.gradle
|
|
||||||
|
|
||||||
notifications:
|
jobs:
|
||||||
email:
|
include:
|
||||||
- skylot@gmail.com
|
- 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
|
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
|
Copyright (c) 2012, Robert Futrell
|
||||||
@@ -174,8 +175,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
*******************************************************************************
|
*******************************************************************************
|
||||||
|
|
||||||
|
|
||||||
Icons copied from several places:
|
Concurrent Trees (https://code.google.com/p/concurrent-trees/)
|
||||||
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
licenced under Apache License 2.0:
|
||||||
- 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/)
|
|
||||||
|
*******************************************************************************
|
||||||
|
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/
|
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
|
## JADX
|
||||||
|
|
||||||
[](https://travis-ci.org/skylot/jadx)
|
[](https://travis-ci.com/skylot/jadx)
|
||||||
[](https://drone.io/github.com/skylot/jadx/latest)
|
[](https://codecov.io/gh/skylot/jadx)
|
||||||
[](https://coveralls.io/r/skylot/jadx)
|
[](https://lgtm.com/projects/g/skylot/jadx/alerts/)
|
||||||
[](https://scan.coverity.com/projects/2166)
|
[](https://sonarcloud.io/dashboard?id=jadx)
|
||||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
[](https://github.com/semantic-release/semantic-release)
|
||||||
|
|
||||||
**jadx** - Dex to Java decompiler
|
**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
|
**jadx-gui features:**
|
||||||
- [unstable](https://drone.io/github.com/skylot/jadx/files)
|
- view decompiled code with highlighted syntax
|
||||||
- from [github](https://github.com/skylot/jadx/releases)
|
- jump to declaration
|
||||||
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
|
- 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`)
|
(on Windows, use `gradlew.bat` instead of `./gradlew`)
|
||||||
|
|
||||||
Scripts for run jadx will be placed in `build/jadx/bin`
|
Scripts for run jadx will be placed in `build/jadx/bin`
|
||||||
and also packed to `build/jadx-<version>.zip`
|
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
|
### 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:
|
options:
|
||||||
-d, --output-dir - output directory
|
-d, --output-dir - output directory
|
||||||
-j, --threads-count - processing threads count
|
-ds, --output-dir-src - output directory for sources
|
||||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
-dr, --output-dir-res - output directory for resources
|
||||||
-r, --no-res - do not decode resources
|
-r, --no-res - do not decode resources
|
||||||
-s, --no-src - do not decompile source code
|
-s, --no-src - do not decompile source code
|
||||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
--single-class - decompile a single class
|
||||||
--cfg - save methods control flow graph to dot file
|
--output-format - can be 'java' or 'json', default: java
|
||||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
-e, --export-gradle - save as android gradle project
|
||||||
-v, --verbose - verbose output
|
-j, --threads-count - processing threads count, default: 4
|
||||||
--deobf - activate deobfuscation
|
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||||
--deobf-min - min length of name
|
--no-imports - disable use of imports, always write entire package name
|
||||||
--deobf-max - max length of name
|
--no-debug-info - disable debug info
|
||||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
--no-inline-anonymous - disable anonymous classes inline
|
||||||
-h, --help - print this help
|
--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:
|
Example:
|
||||||
jadx -d out classes.dex
|
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
|
### Troubleshooting
|
||||||
##### Out of memory error:
|
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
|
||||||
- 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
|
|
||||||
|
|
||||||
|
### Contributing
|
||||||
To support this project you can:
|
To support this project you can:
|
||||||
- Post thoughts about new features/optimizations that important to you
|
- Post thoughts about new features/optimizations that important to you
|
||||||
- Submit bug using one of following patterns:
|
- Submit decompilation issues, please read before proceed: [Open issue](CONTRIBUTING.md#Open-Issue)
|
||||||
* Java code examples which decompiles incorrectly
|
- Open pull request, please follow these rules: [Pull Request Process](CONTRIBUTING.md#Pull-Request-Process)
|
||||||
* Error log and link to _public available_ apk file or app page on Google play
|
|
||||||
|
|
||||||
And any other comments will be very helpfull,
|
### Related projects:
|
||||||
because at current stage of development it is very time consuming
|
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
|
||||||
to **find** new bugs, design and implement new features.
|
|
||||||
Also I need to **prioritize** these task for complete most important at first.
|
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
*Licensed under the Apache 2.0 License*
|
*Licensed under the Apache 2.0 License*
|
||||||
|
|
||||||
*Copyright 2015 by Skylot*
|
|
||||||
|
|||||||
+136
-75
@@ -1,109 +1,170 @@
|
|||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "com.github.kt3k.coveralls" version "2.3.1"
|
id 'org.sonarqube' version '3.0'
|
||||||
id "info.solidsoft.pitest" version "1.1.4"
|
id 'com.github.ben-manes.versions' version '0.33.0'
|
||||||
// id "com.github.ben-manes.versions" version "0.8"
|
id "com.diffplug.spotless" version "5.5.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'sonar-runner'
|
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||||
|
|
||||||
ext.jadxVersion = file('version').readLines().get(0)
|
|
||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
println("jadx version: ${jadxVersion}")
|
||||||
|
|
||||||
subprojects {
|
allprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'groovy'
|
apply plugin: 'jacoco'
|
||||||
apply plugin: 'jacoco'
|
apply plugin: 'checkstyle'
|
||||||
apply plugin: 'com.github.kt3k.coveralls'
|
|
||||||
|
|
||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
tasks.withType(JavaCompile) {
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_6
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_6
|
|
||||||
|
|
||||||
if (!"$it".contains(':jadx-samples:')) {
|
tasks.withType(JavaCompile) {
|
||||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
if (!"$it".contains(':jadx-samples:')) {
|
||||||
}
|
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
jar {
|
compileJava {
|
||||||
version = jadxVersion
|
options.encoding = "UTF-8"
|
||||||
manifest {
|
}
|
||||||
mainAttributes('jadx-version': jadxVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
jar {
|
||||||
compile 'org.slf4j:slf4j-api:1.7.10'
|
manifest {
|
||||||
|
mainAttributes('jadx-version': jadxVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
testCompile 'ch.qos.logback:logback-classic:1.1.2'
|
dependencies {
|
||||||
testCompile 'junit:junit:4.12'
|
implementation 'org.slf4j:slf4j-api:1.7.30'
|
||||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
compileOnly 'org.jetbrains:annotations:20.1.0'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
|
||||||
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
|
|
||||||
testCompile 'cglib:cglib-nodep:3.1'
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
testImplementation 'ch.qos.logback:logback-classic:1.2.3'
|
||||||
mavenCentral()
|
testImplementation 'org.hamcrest:hamcrest-library:2.2'
|
||||||
mavenLocal()
|
testImplementation 'org.mockito:mockito-core:3.5.10'
|
||||||
jcenter()
|
testImplementation 'org.assertj:assertj-core:3.17.2'
|
||||||
}
|
|
||||||
|
|
||||||
jacocoTestReport {
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
|
||||||
reports {
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
|
||||||
xml.enabled = true // coveralls plugin depends on xml format report
|
|
||||||
html.enabled = true
|
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 */
|
sonarqube {
|
||||||
repositories {
|
properties {
|
||||||
mavenCentral()
|
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']) {
|
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
|
||||||
destinationDir file("$buildDir/jadx")
|
destinationDir file("$buildDir/jadx")
|
||||||
['jadx-cli', 'jadx-gui'].each {
|
['jadx-cli', 'jadx-gui'].each {
|
||||||
from tasks.getByPath(":${it}:installDist").destinationDir
|
from tasks.getByPath(":${it}:installDist").destinationDir
|
||||||
}
|
}
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
task pack(type: Zip, dependsOn: copyArtifacts) {
|
task pack(type: Zip, dependsOn: copyArtifacts) {
|
||||||
destinationDir buildDir
|
destinationDirectory = buildDir
|
||||||
archiveName "jadx-${jadxVersion}.zip"
|
archiveFileName = "jadx-${jadxVersion}.zip"
|
||||||
from copyArtifacts.destinationDir
|
from copyArtifacts.destinationDir
|
||||||
}
|
}
|
||||||
|
|
||||||
task dist(dependsOn: pack) {
|
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
|
||||||
description = 'Build jadx distribution zip'
|
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 samples(dependsOn: 'jadx-samples:samples') {
|
||||||
}
|
group 'jadx'
|
||||||
|
|
||||||
task pitest(overwrite: true, dependsOn: 'jadx-core:pitest') {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task cleanBuildDir(type: Delete) {
|
task cleanBuildDir(type: Delete) {
|
||||||
delete buildDir
|
group 'jadx'
|
||||||
|
delete buildDir
|
||||||
}
|
}
|
||||||
|
|
||||||
build.dependsOn(dist, samples)
|
test.dependsOn(samples)
|
||||||
|
|
||||||
clean.dependsOn(cleanBuildDir)
|
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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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
|
# Attempt to set APP_HOME
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
PRG="$0"
|
||||||
@@ -61,12 +36,53 @@ while [ -h "$PRG" ] ; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
SAVED="`pwd`"
|
||||||
cd "`dirname \"$PRG\"`/" >&-
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
APP_HOME="`pwd -P`"
|
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
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@@ -90,7 +106,7 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# 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`
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
if [ $? -eq 0 ] ; then
|
if [ $? -eq 0 ] ; then
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; 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\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
SEP=""
|
SEP=""
|
||||||
@@ -138,27 +156,30 @@ if $cygwin ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
# Escape application args
|
||||||
function splitJvmOpts() {
|
save () {
|
||||||
JVM_OPTS=("$@")
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
}
|
}
|
||||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
APP_ARGS=`save "$@"`
|
||||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
|
||||||
|
|
||||||
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
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@@ -8,20 +24,23 @@
|
|||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables with windows NT shell
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
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
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
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
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
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_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@@ -45,34 +64,14 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
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
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@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
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
+23
-14
@@ -1,20 +1,29 @@
|
|||||||
apply plugin: 'application'
|
plugins {
|
||||||
|
id 'application'
|
||||||
mainClassName = 'jadx.cli.JadxCLI'
|
}
|
||||||
applicationName = 'jadx'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile(project(':jadx-core'))
|
implementation(project(':jadx-core'))
|
||||||
compile 'com.beust:jcommander:1.47'
|
|
||||||
compile 'ch.qos.logback:logback-classic:1.1.2'
|
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 {
|
applicationDistribution.with {
|
||||||
into('') {
|
into('') {
|
||||||
from '../.'
|
from '../.'
|
||||||
include 'README.md'
|
include 'README.md'
|
||||||
include 'NOTICE'
|
include 'NOTICE'
|
||||||
include 'LICENSE'
|
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;
|
package jadx.cli;
|
||||||
|
|
||||||
import jadx.api.JadxDecompiler;
|
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
public class JadxCLI {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
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 {
|
try {
|
||||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
result = execute(args);
|
||||||
if (processArgs(jadxArgs, args)) {
|
} catch (JadxArgsValidateException e) {
|
||||||
processAndSave(jadxArgs);
|
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||||
}
|
result = 1;
|
||||||
} catch (Throwable e) {
|
} catch (Exception e) {
|
||||||
LOG.error("jadx error: {}", e.getMessage(), 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 {
|
public static int execute(String[] args) {
|
||||||
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
|
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||||
jadx.setOutputDir(jadxArgs.getOutDir());
|
if (jadxArgs.processArgs(args)) {
|
||||||
jadx.loadFiles(jadxArgs.getInput());
|
return processAndSave(jadxArgs.toJadxArgs());
|
||||||
jadx.save();
|
|
||||||
if (jadx.getErrorsCount() != 0) {
|
|
||||||
jadx.printErrorsReport();
|
|
||||||
LOG.error("finished with errors");
|
|
||||||
} else {
|
|
||||||
LOG.info("done");
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
|
private static int processAndSave(JadxArgs jadxArgs) {
|
||||||
if (!jadxArgs.processArgs(args)) {
|
jadxArgs.setCodeCache(new NoOpCodeCache());
|
||||||
return false;
|
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
|
||||||
}
|
jadx.load();
|
||||||
if (jadxArgs.getInput().isEmpty()) {
|
jadx.save();
|
||||||
LOG.error("Please specify input file");
|
int errorsCount = jadx.getErrorsCount();
|
||||||
jadxArgs.printUsage();
|
if (errorsCount != 0) {
|
||||||
return false;
|
jadx.printErrorsReport();
|
||||||
}
|
LOG.error("finished with errors, count: {}", errorsCount);
|
||||||
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);
|
|
||||||
} else {
|
} 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()) {
|
return 0;
|
||||||
throw new JadxException("Output directory exists as file " + outputDir);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,245 +1,360 @@
|
|||||||
package jadx.cli;
|
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.api.JadxDecompiler;
|
||||||
import jadx.core.utils.exceptions.JadxException;
|
import jadx.core.utils.exceptions.JadxException;
|
||||||
|
import jadx.core.utils.files.FileUtils;
|
||||||
|
|
||||||
import java.io.File;
|
public class JadxCLIArgs {
|
||||||
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;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
|
||||||
import org.slf4j.LoggerFactory;
|
protected List<String> files = new ArrayList<>(1);
|
||||||
|
|
||||||
import com.beust.jcommander.JCommander;
|
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||||
import com.beust.jcommander.Parameter;
|
protected String outDir;
|
||||||
import com.beust.jcommander.ParameterDescription;
|
|
||||||
import com.beust.jcommander.ParameterException;
|
|
||||||
|
|
||||||
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)")
|
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
|
||||||
protected List<String> files;
|
protected String outDirRes;
|
||||||
|
|
||||||
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
|
@Parameter(names = { "-r", "--no-res" }, description = "do not decode resources")
|
||||||
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")
|
|
||||||
protected boolean skipResources = false;
|
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;
|
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;
|
protected boolean showInconsistentCode = false;
|
||||||
|
|
||||||
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
|
@Parameter(names = { "--no-imports" }, description = "disable use of imports, always write entire package name")
|
||||||
protected boolean cfgOutput = false;
|
protected boolean useImports = true;
|
||||||
|
|
||||||
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
|
@Parameter(names = { "--no-debug-info" }, description = "disable debug info")
|
||||||
protected boolean rawCfgOutput = false;
|
protected boolean debugInfo = true;
|
||||||
|
|
||||||
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
|
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
|
||||||
protected boolean verbose = false;
|
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;
|
protected boolean deobfuscationOn = false;
|
||||||
|
|
||||||
@Parameter(names = {"--deobf-min"}, description = "min length of name")
|
@Parameter(names = { "--deobf-min" }, description = "min length of name, renamed if shorter")
|
||||||
protected int deobfuscationMinLength = 2;
|
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;
|
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;
|
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;
|
protected boolean printHelp = false;
|
||||||
|
|
||||||
private final List<File> input = new ArrayList<File>(1);
|
|
||||||
private File outputDir;
|
|
||||||
|
|
||||||
public boolean processArgs(String[] args) {
|
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 {
|
* Set values only for options provided in cmd.
|
||||||
new JCommander(this, args);
|
* Used to merge saved options and options passed in command line.
|
||||||
return true;
|
*/
|
||||||
} catch (ParameterException e) {
|
public boolean overrideProvided(String[] args) {
|
||||||
System.err.println("Arguments parse error: " + e.getMessage());
|
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(newInstance());
|
||||||
printUsage();
|
if (!jcw.parse(args)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
jcw.overrideProvided(this);
|
||||||
|
return process(jcw);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean process() {
|
protected JadxCLIArgs newInstance() {
|
||||||
if (isPrintHelp()) {
|
return new JadxCLIArgs();
|
||||||
printUsage();
|
}
|
||||||
|
|
||||||
|
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
|
||||||
|
if (printHelp) {
|
||||||
|
jcw.printUsage();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (printVersion) {
|
||||||
|
System.out.println(JadxDecompiler.getVersion());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (threadsCount <= 0) {
|
if (threadsCount <= 0) {
|
||||||
throw new JadxException("Threads count must be positive");
|
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
LogHelper.setLogLevelFromArgs(this);
|
||||||
} catch (JadxException e) {
|
} catch (JadxException e) {
|
||||||
System.err.println("ERROR: " + e.getMessage());
|
System.err.println("ERROR: " + e.getMessage());
|
||||||
printUsage();
|
jcw.printUsage();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void printUsage() {
|
public JadxArgs toJadxArgs() {
|
||||||
JCommander jc = new JCommander(this);
|
JadxArgs args = new JadxArgs();
|
||||||
// print usage in not sorted fields order (by default its sorted by description)
|
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
|
||||||
PrintStream out = System.out;
|
args.setOutDir(FileUtils.toFile(outDir));
|
||||||
out.println();
|
args.setOutDirSrc(FileUtils.toFile(outDirSrc));
|
||||||
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
args.setOutDirRes(FileUtils.toFile(outDirRes));
|
||||||
out.println();
|
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
||||||
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
|
args.setThreadsCount(threadsCount);
|
||||||
out.println("options:");
|
args.setSkipSources(skipSources);
|
||||||
|
if (singleClass != null) {
|
||||||
List<ParameterDescription> params = jc.getParameters();
|
args.setClassFilter(className -> singleClass.equals(className));
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Field[] fields = JadxCLIArgs.class.getDeclaredFields();
|
args.setSkipResources(skipResources);
|
||||||
for (Field f : fields) {
|
args.setFallbackMode(fallbackMode);
|
||||||
String name = f.getName();
|
args.setShowInconsistentCode(showInconsistentCode);
|
||||||
ParameterDescription p = paramsMap.get(name);
|
args.setCfgOutput(cfgOutput);
|
||||||
if (p == null) {
|
args.setRawCFGOutput(rawCfgOutput);
|
||||||
continue;
|
args.setReplaceConsts(replaceConsts);
|
||||||
}
|
args.setDeobfuscationOn(deobfuscationOn);
|
||||||
StringBuilder opt = new StringBuilder();
|
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
||||||
opt.append(' ').append(p.getNames());
|
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||||
addSpaces(opt, maxNamesLen - opt.length() + 2);
|
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||||
opt.append("- ").append(p.getDescription());
|
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||||
out.println(opt);
|
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
|
||||||
}
|
args.setEscapeUnicode(escapeUnicode);
|
||||||
out.println("Example:");
|
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||||
out.println(" jadx -d out classes.dex");
|
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) {
|
public List<String> getFiles() {
|
||||||
for (int i = 0; i < count; i++) {
|
return files;
|
||||||
str.append(' ');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<File> getInput() {
|
public String getOutDir() {
|
||||||
return input;
|
return outDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public String getOutDirSrc() {
|
||||||
public File getOutDir() {
|
return outDirSrc;
|
||||||
return outputDir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOutputDir(File outputDir) {
|
public String getOutDirRes() {
|
||||||
this.outputDir = outputDir;
|
return outDirRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPrintHelp() {
|
|
||||||
return printHelp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSkipResources() {
|
public boolean isSkipResources() {
|
||||||
return skipResources;
|
return skipResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSkipSources() {
|
public boolean isSkipSources() {
|
||||||
return skipSources;
|
return skipSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getThreadsCount() {
|
public int getThreadsCount() {
|
||||||
return threadsCount;
|
return threadsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCFGOutput() {
|
|
||||||
return cfgOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRawCFGOutput() {
|
|
||||||
return rawCfgOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFallbackMode() {
|
public boolean isFallbackMode() {
|
||||||
return fallbackMode;
|
return fallbackMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isShowInconsistentCode() {
|
public boolean isShowInconsistentCode() {
|
||||||
return showInconsistentCode;
|
return showInconsistentCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public boolean isUseImports() {
|
||||||
public boolean isVerbose() {
|
return useImports;
|
||||||
return verbose;
|
}
|
||||||
|
|
||||||
|
public boolean isDebugInfo() {
|
||||||
|
return debugInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInlineAnonymousClasses() {
|
||||||
|
return inlineAnonymousClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDeobfuscationOn() {
|
public boolean isDeobfuscationOn() {
|
||||||
return deobfuscationOn;
|
return deobfuscationOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDeobfuscationMinLength() {
|
public int getDeobfuscationMinLength() {
|
||||||
return deobfuscationMinLength;
|
return deobfuscationMinLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDeobfuscationMaxLength() {
|
public int getDeobfuscationMaxLength() {
|
||||||
return deobfuscationMaxLength;
|
return deobfuscationMaxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDeobfuscationForceSave() {
|
public boolean isDeobfuscationForceSave() {
|
||||||
return deobfuscationForceSave;
|
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>
|
<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">
|
<root level="INFO">
|
||||||
<encoder>
|
<appender-ref ref="STDOUT"/>
|
||||||
<pattern>%-5level - %msg%n</pattern>
|
</root>
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="STDOUT"/>
|
|
||||||
</root>
|
|
||||||
|
|
||||||
</configuration>
|
</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'
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
apply plugin: "info.solidsoft.pitest"
|
}
|
||||||
|
|
||||||
dependencies {
|
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')
|
api(project(':jadx-plugins:jadx-plugins-api'))
|
||||||
compile 'commons-io:commons-io:2.4'
|
|
||||||
compile 'org.ow2.asm:asm:5.0.3'
|
|
||||||
compile 'com.intellij:annotations:12.0'
|
|
||||||
|
|
||||||
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) {
|
test {
|
||||||
classifier = 'tests'
|
exclude '**/tmp/*'
|
||||||
from sourceSets.test.output
|
|
||||||
}
|
|
||||||
|
|
||||||
pitest {
|
|
||||||
excludedMethods = ['toString']
|
|
||||||
threads = 4
|
|
||||||
enableDefaultIncrementalAnalysis = true
|
|
||||||
outputFormats = ['XML', 'HTML']
|
|
||||||
jvmArgs = ['-Xmx12G']
|
|
||||||
}
|
}
|
||||||
|
|||||||
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 {
|
public final class CodePosition {
|
||||||
|
|
||||||
private final JavaClass cls;
|
private final JavaNode node;
|
||||||
private final int line;
|
private final int line;
|
||||||
private final int offset;
|
private final int offset;
|
||||||
|
|
||||||
public CodePosition(JavaClass cls, int line, int offset) {
|
public CodePosition(JavaNode node, int line, int offset) {
|
||||||
this.cls = cls;
|
this.node = node;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CodePosition(int line, int offset) {
|
public CodePosition(int line, int offset) {
|
||||||
this.cls = null;
|
this.node = null;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaNode getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
public JavaClass getJavaClass() {
|
public JavaClass getJavaClass() {
|
||||||
return cls;
|
JavaClass parent = node.getDeclaringClass();
|
||||||
|
if (parent == null && node instanceof JavaClass) {
|
||||||
|
return (JavaClass) node;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLine() {
|
public int getLine() {
|
||||||
@@ -30,10 +38,6 @@ public final class CodePosition {
|
|||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSet() {
|
|
||||||
return line != 0 || offset != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
@@ -53,6 +57,14 @@ public final class CodePosition {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
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;
|
package jadx.api;
|
||||||
|
|
||||||
import jadx.core.Jadx;
|
import java.io.Closeable;
|
||||||
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.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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:
|
* Jadx API usage example:
|
||||||
* <pre><code>
|
*
|
||||||
* JadxDecompiler jadx = new JadxDecompiler();
|
* <pre>
|
||||||
* jadx.loadFile(new File("classes.dex"));
|
* <code>
|
||||||
* jadx.setOutputDir(new File("out"));
|
* JadxArgs args = new JadxArgs();
|
||||||
* jadx.save();
|
* args.getInputFiles().add(new File("test.apk"));
|
||||||
* </code></pre>
|
* args.setOutDir(new File("jadx-test-output"));
|
||||||
* <p/>
|
* try (JadxDecompiler jadx = new JadxDecompiler(args)) {
|
||||||
* Instead of 'save()' you can get list of decompiled classes:
|
* jadx.load();
|
||||||
* <pre><code>
|
* jadx.save();
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Instead of 'save()' you can iterate over decompiled classes:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
* for(JavaClass cls : jadx.getClasses()) {
|
* for(JavaClass cls : jadx.getClasses()) {
|
||||||
* System.out.println(cls.getCode());
|
* 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 static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||||
|
|
||||||
private final IJadxArgs args;
|
private final JadxArgs args;
|
||||||
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
|
private final JadxPluginManager pluginManager = new JadxPluginManager();
|
||||||
|
private final List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||||
private File outDir;
|
|
||||||
|
|
||||||
private RootNode root;
|
private RootNode root;
|
||||||
private List<IDexTreeVisitor> passes;
|
|
||||||
private CodeGen codeGen;
|
|
||||||
|
|
||||||
private List<JavaClass> classes;
|
private List<JavaClass> classes;
|
||||||
private List<ResourceFile> resources;
|
private List<ResourceFile> resources;
|
||||||
|
|
||||||
private BinaryXMLParser xmlParser;
|
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() {
|
public JadxDecompiler() {
|
||||||
this(new DefaultJadxArgs());
|
this(new JadxArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
public JadxDecompiler(IJadxArgs jadxArgs) {
|
public JadxDecompiler(JadxArgs args) {
|
||||||
this.args = jadxArgs;
|
this.args = args;
|
||||||
this.outDir = jadxArgs.getOutDir();
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
reset();
|
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) {
|
private void loadInputFiles() {
|
||||||
this.outDir = outDir;
|
loadedInputs.clear();
|
||||||
init();
|
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||||
}
|
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||||
|
ILoadResult loadResult = inputPlugin.loadFiles(inputPaths);
|
||||||
void init() {
|
if (loadResult != null && !loadResult.isEmpty()) {
|
||||||
if (outDir == null) {
|
loadedInputs.add(loadResult);
|
||||||
outDir = new DefaultJadxArgs().getOutDir();
|
}
|
||||||
}
|
}
|
||||||
this.passes = Jadx.getPassesList(args, outDir);
|
|
||||||
this.codeGen = new CodeGen(args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
private void reset() {
|
||||||
|
root = null;
|
||||||
classes = null;
|
classes = null;
|
||||||
resources = null;
|
resources = null;
|
||||||
xmlParser = null;
|
xmlParser = null;
|
||||||
root = null;
|
|
||||||
passes = null;
|
classesMap.clear();
|
||||||
codeGen = null;
|
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() {
|
public static String getVersion() {
|
||||||
return Jadx.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() {
|
public void save() {
|
||||||
save(!args.isSkipSources(), !args.isSkipResources());
|
save(!args.isSkipSources(), !args.isSkipResources());
|
||||||
}
|
}
|
||||||
@@ -131,12 +166,13 @@ public final class JadxDecompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void save(boolean saveSources, boolean saveResources) {
|
private void save(boolean saveSources, boolean saveResources) {
|
||||||
|
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||||
|
ex.shutdown();
|
||||||
try {
|
try {
|
||||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
|
||||||
ex.shutdown();
|
|
||||||
ex.awaitTermination(1, TimeUnit.DAYS);
|
ex.awaitTermination(1, TimeUnit.DAYS);
|
||||||
} catch (InterruptedException e) {
|
} 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 ...");
|
LOG.info("processing ...");
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||||
if (saveSources) {
|
|
||||||
for (final JavaClass cls : getClasses()) {
|
File sourcesOutDir;
|
||||||
executor.execute(new Runnable() {
|
File resOutDir;
|
||||||
@Override
|
if (args.isExportAsGradleProject()) {
|
||||||
public void run() {
|
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
|
||||||
cls.decompile();
|
export.init();
|
||||||
SaveCode.save(outDir, args, cls.getClassNode());
|
sourcesOutDir = export.getSrcOutDir();
|
||||||
}
|
resOutDir = export.getResOutDir();
|
||||||
});
|
} else {
|
||||||
}
|
sourcesOutDir = args.getOutDirSrc();
|
||||||
|
resOutDir = args.getOutDirRes();
|
||||||
}
|
}
|
||||||
if (saveResources) {
|
if (saveResources) {
|
||||||
for (final ResourceFile resourceFile : getResources()) {
|
appendResourcesSave(executor, resOutDir);
|
||||||
executor.execute(new Runnable() {
|
}
|
||||||
@Override
|
if (saveSources) {
|
||||||
public void run() {
|
appendSourcesSave(executor, sourcesOutDir);
|
||||||
if (ResourceType.isSupportedForUnpack(resourceFile.getType())) {
|
|
||||||
CodeWriter cw = resourceFile.getContent();
|
|
||||||
if (cw != null) {
|
|
||||||
cw.save(new File(outDir, resourceFile.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return executor;
|
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() {
|
public List<JavaClass> getClasses() {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
if (classes == null) {
|
if (classes == null) {
|
||||||
List<ClassNode> classNodeList = root.getClasses(false);
|
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) {
|
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);
|
classes = Collections.unmodifiableList(clsList);
|
||||||
}
|
}
|
||||||
@@ -202,7 +267,7 @@ public final class JadxDecompiler {
|
|||||||
if (root == null) {
|
if (root == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
resources = new ResourcesLoader(this).load(inputFiles);
|
resources = new ResourcesLoader(this).load();
|
||||||
}
|
}
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
@@ -212,28 +277,19 @@ public final class JadxDecompiler {
|
|||||||
if (classList.isEmpty()) {
|
if (classList.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
|
Map<String, List<JavaClass>> map = new HashMap<>();
|
||||||
for (JavaClass javaClass : classList) {
|
for (JavaClass javaClass : classList) {
|
||||||
String pkg = javaClass.getPackage();
|
String pkg = javaClass.getPackage();
|
||||||
List<JavaClass> clsList = map.get(pkg);
|
List<JavaClass> clsList = map.computeIfAbsent(pkg, k -> new ArrayList<>());
|
||||||
if (clsList == null) {
|
|
||||||
clsList = new ArrayList<JavaClass>();
|
|
||||||
map.put(pkg, clsList);
|
|
||||||
}
|
|
||||||
clsList.add(javaClass);
|
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()) {
|
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
|
||||||
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
|
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
|
||||||
}
|
}
|
||||||
Collections.sort(packages);
|
Collections.sort(packages);
|
||||||
for (JavaPackage pkg : packages) {
|
for (JavaPackage pkg : packages) {
|
||||||
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
|
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName, String.CASE_INSENSITIVE_ORDER));
|
||||||
@Override
|
|
||||||
public int compare(JavaClass o1, JavaClass o2) {
|
|
||||||
return o1.getName().compareTo(o2.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return Collections.unmodifiableList(packages);
|
return Collections.unmodifiableList(packages);
|
||||||
}
|
}
|
||||||
@@ -245,66 +301,172 @@ public final class JadxDecompiler {
|
|||||||
return root.getErrorsCounter().getErrorCount();
|
return root.getErrorsCounter().getErrorCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getWarnsCount() {
|
||||||
|
if (root == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return root.getErrorsCounter().getWarnsCount();
|
||||||
|
}
|
||||||
|
|
||||||
public void printErrorsReport() {
|
public void printErrorsReport() {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
root.getClsp().printMissingClasses();
|
||||||
root.getErrorsCounter().printReport();
|
root.getErrorsCounter().printReport();
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse() throws DecodeException {
|
/**
|
||||||
reset();
|
* Internal API. Not Stable!
|
||||||
init();
|
*/
|
||||||
|
public RootNode getRoot() {
|
||||||
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() {
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
BinaryXMLParser getXmlParser() {
|
synchronized BinaryXMLParser getXmlParser() {
|
||||||
if (xmlParser == null) {
|
if (xmlParser == null) {
|
||||||
xmlParser = new BinaryXMLParser(root);
|
xmlParser = new BinaryXMLParser(root);
|
||||||
}
|
}
|
||||||
return xmlParser;
|
return xmlParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
JavaClass findJavaClass(ClassNode cls) {
|
private void loadJavaClass(JavaClass javaClass) {
|
||||||
if (cls == null) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
for (JavaClass javaClass : getClasses()) {
|
if (parentClass != cls) {
|
||||||
if (javaClass.getClassNode().equals(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 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;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
package jadx.api;
|
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.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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 {
|
public final class JavaClass implements JavaNode {
|
||||||
|
|
||||||
private final JadxDecompiler decompiler;
|
private final JadxDecompiler decompiler;
|
||||||
@@ -23,6 +24,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
private List<JavaClass> innerClasses = Collections.emptyList();
|
private List<JavaClass> innerClasses = Collections.emptyList();
|
||||||
private List<JavaField> fields = Collections.emptyList();
|
private List<JavaField> fields = Collections.emptyList();
|
||||||
private List<JavaMethod> methods = Collections.emptyList();
|
private List<JavaMethod> methods = Collections.emptyList();
|
||||||
|
private boolean listsLoaded;
|
||||||
|
|
||||||
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
|
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
|
||||||
this.decompiler = decompiler;
|
this.decompiler = decompiler;
|
||||||
@@ -40,39 +42,56 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getCode() {
|
public String getCode() {
|
||||||
CodeWriter code = cls.getCode();
|
ICodeInfo code = getCodeInfo();
|
||||||
if (code == null) {
|
|
||||||
decompile();
|
|
||||||
code = cls.getCode();
|
|
||||||
}
|
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return code.toString();
|
return code.getCodeStr();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICodeInfo getCodeInfo() {
|
||||||
|
return cls.decompile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decompile() {
|
public void decompile() {
|
||||||
if (decompiler == null) {
|
cls.decompile();
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (cls.getCode() == null) {
|
|
||||||
decompiler.processClass(cls);
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() {
|
private synchronized void loadLists() {
|
||||||
|
if (listsLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listsLoaded = true;
|
||||||
|
decompile();
|
||||||
|
|
||||||
int inClsCount = cls.getInnerClasses().size();
|
int inClsCount = cls.getInnerClasses().size();
|
||||||
if (inClsCount != 0) {
|
if (inClsCount != 0) {
|
||||||
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
|
List<JavaClass> list = new ArrayList<>(inClsCount);
|
||||||
for (ClassNode inner : cls.getInnerClasses()) {
|
for (ClassNode inner : cls.getInnerClasses()) {
|
||||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||||
JavaClass javaClass = new JavaClass(inner, this);
|
JavaClass javaClass = new JavaClass(inner, this);
|
||||||
javaClass.load();
|
javaClass.loadLists();
|
||||||
list.add(javaClass);
|
list.add(javaClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,10 +100,11 @@ public final class JavaClass implements JavaNode {
|
|||||||
|
|
||||||
int fieldsCount = cls.getFields().size();
|
int fieldsCount = cls.getFields().size();
|
||||||
if (fieldsCount != 0) {
|
if (fieldsCount != 0) {
|
||||||
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
|
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||||
for (FieldNode f : cls.getFields()) {
|
for (FieldNode f : cls.getFields()) {
|
||||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
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);
|
this.fields = Collections.unmodifiableList(flds);
|
||||||
@@ -92,63 +112,69 @@ public final class JavaClass implements JavaNode {
|
|||||||
|
|
||||||
int methodsCount = cls.getMethods().size();
|
int methodsCount = cls.getMethods().size();
|
||||||
if (methodsCount != 0) {
|
if (methodsCount != 0) {
|
||||||
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
|
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
||||||
for (MethodNode m : cls.getMethods()) {
|
for (MethodNode m : cls.getMethods()) {
|
||||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
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>() {
|
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||||
@Override
|
|
||||||
public int compare(JavaMethod o1, JavaMethod o2) {
|
|
||||||
return o1.getName().compareTo(o2.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.methods = Collections.unmodifiableList(mths);
|
this.methods = Collections.unmodifiableList(mths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
protected JadxDecompiler getRootDecompiler() {
|
||||||
decompile();
|
if (parent != null) {
|
||||||
return cls.getCode().getAnnotations();
|
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();
|
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||||
if (map.isEmpty()) {
|
if (map.isEmpty() || decompiler == null) {
|
||||||
return null;
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
Object obj = map.get(new CodePosition(line, offset));
|
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
|
||||||
if (!(obj instanceof LineAttrNode)) {
|
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||||
return null;
|
CodePosition codePosition = entry.getKey();
|
||||||
|
Object obj = entry.getValue();
|
||||||
|
JavaNode node = getRootDecompiler().convertNode(obj);
|
||||||
|
if (node != null) {
|
||||||
|
resultMap.put(codePosition, node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ClassNode clsNode = null;
|
return resultMap;
|
||||||
if (obj instanceof ClassNode) {
|
}
|
||||||
clsNode = (ClassNode) obj;
|
|
||||||
} else if (obj instanceof MethodNode) {
|
@Override
|
||||||
clsNode = ((MethodNode) obj).getParentClass();
|
public List<JavaNode> getUseIn() {
|
||||||
} else if (obj instanceof FieldNode) {
|
return getRootDecompiler().convertNodes(cls.getUseIn());
|
||||||
clsNode = ((FieldNode) obj).getParentClass();
|
}
|
||||||
}
|
|
||||||
if (clsNode == null) {
|
@Nullable
|
||||||
return null;
|
@Deprecated
|
||||||
}
|
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||||
clsNode = clsNode.getTopParentClass();
|
return getRootDecompiler().getJavaNodeAtPosition(getCodeInfo(), line, offset);
|
||||||
JavaClass jCls = decompiler.findJavaClass(clsNode);
|
}
|
||||||
if (jCls == null) {
|
|
||||||
return null;
|
@Nullable
|
||||||
}
|
@Deprecated
|
||||||
jCls.decompile();
|
public CodePosition getDefinitionPosition() {
|
||||||
int defLine = ((LineAttrNode) obj).getDecompiledLine();
|
return getRootDecompiler().getDefinitionPosition(this);
|
||||||
if (defLine == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new CodePosition(jCls, defLine, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSourceLine(int decompiledLine) {
|
public Integer getSourceLine(int decompiledLine) {
|
||||||
decompile();
|
return getCodeInfo().getLineMapping().get(decompiledLine);
|
||||||
return cls.getCode().getLineMapping().get(decompiledLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -161,6 +187,10 @@ public final class JavaClass implements JavaNode {
|
|||||||
return cls.getFullName();
|
return cls.getFullName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRawName() {
|
||||||
|
return cls.getRawName();
|
||||||
|
}
|
||||||
|
|
||||||
public String getPackage() {
|
public String getPackage() {
|
||||||
return cls.getPackage();
|
return cls.getPackage();
|
||||||
}
|
}
|
||||||
@@ -170,25 +200,31 @@ public final class JavaClass implements JavaNode {
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getTopParentClass() {
|
||||||
|
return parent == null ? this : parent.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
public AccessInfo getAccessInfo() {
|
public AccessInfo getAccessInfo() {
|
||||||
return cls.getAccessFlags();
|
return cls.getAccessFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaClass> getInnerClasses() {
|
public List<JavaClass> getInnerClasses() {
|
||||||
decompile();
|
loadLists();
|
||||||
return innerClasses;
|
return innerClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaField> getFields() {
|
public List<JavaField> getFields() {
|
||||||
decompile();
|
loadLists();
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<JavaMethod> getMethods() {
|
public List<JavaMethod> getMethods() {
|
||||||
decompile();
|
loadLists();
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDecompiledLine() {
|
||||||
return cls.getDecompiledLine();
|
return cls.getDecompiledLine();
|
||||||
}
|
}
|
||||||
@@ -205,6 +241,6 @@ public final class JavaClass implements JavaNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return cls.getFullName() + "[ " + getFullName() + " ]";
|
return getFullName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
@@ -21,7 +23,7 @@ public final class JavaField implements JavaNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFullName() {
|
public String getFullName() {
|
||||||
return parent.getFullName() + "." + getName();
|
return parent.getFullName() + '.' + getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -29,15 +31,48 @@ public final class JavaField implements JavaNode {
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getTopParentClass() {
|
||||||
|
return parent.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
public AccessInfo getAccessFlags() {
|
public AccessInfo getAccessFlags() {
|
||||||
return field.getAccessFlags();
|
return field.getAccessFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArgType getType() {
|
public ArgType getType() {
|
||||||
return field.getType();
|
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDecompiledLine() {
|
||||||
return field.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;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import jadx.core.dex.info.AccessInfo;
|
import jadx.core.dex.info.AccessInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public final class JavaMethod implements JavaNode {
|
public final class JavaMethod implements JavaNode {
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
@@ -30,16 +32,33 @@ public final class JavaMethod implements JavaNode {
|
|||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getTopParentClass() {
|
||||||
|
return parent.getTopParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
public AccessInfo getAccessFlags() {
|
public AccessInfo getAccessFlags() {
|
||||||
return mth.getAccessFlags();
|
return mth.getAccessFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ArgType> getArguments() {
|
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() {
|
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() {
|
public boolean isConstructor() {
|
||||||
@@ -50,7 +69,30 @@ public final class JavaMethod implements JavaNode {
|
|||||||
return mth.getMethodInfo().isClassInit();
|
return mth.getMethodInfo().isClassInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getDecompiledLine() {
|
public int getDecompiledLine() {
|
||||||
return mth.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;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface JavaNode {
|
public interface JavaNode {
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
@@ -7,4 +9,10 @@ public interface JavaNode {
|
|||||||
String getFullName();
|
String getFullName();
|
||||||
|
|
||||||
JavaClass getDeclaringClass();
|
JavaClass getDeclaringClass();
|
||||||
|
|
||||||
|
JavaClass getTopParentClass();
|
||||||
|
|
||||||
|
int getDecompiledLine();
|
||||||
|
|
||||||
|
List<JavaNode> getUseIn();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -33,6 +34,21 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaClass getTopParentClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDecompiledLine() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<JavaNode> getUseIn() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NotNull JavaPackage o) {
|
public int compareTo(@NotNull JavaPackage o) {
|
||||||
return name.compareTo(o.name);
|
return name.compareTo(o.name);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package jadx.api;
|
package jadx.api;
|
||||||
|
|
||||||
import jadx.core.codegen.CodeWriter;
|
|
||||||
|
|
||||||
import java.io.File;
|
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 class ResourceFile {
|
||||||
|
|
||||||
public static final class ZipRef {
|
public static final class ZipRef {
|
||||||
@@ -33,22 +35,34 @@ public class ResourceFile {
|
|||||||
private final String name;
|
private final String name;
|
||||||
private final ResourceType type;
|
private final ResourceType type;
|
||||||
private ZipRef zipRef;
|
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.decompiler = decompiler;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getOriginalName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDeobfName() {
|
||||||
|
return deobfName != null ? deobfName : name;
|
||||||
|
}
|
||||||
|
|
||||||
public ResourceType getType() {
|
public ResourceType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CodeWriter getContent() {
|
public ResContainer loadContent() {
|
||||||
return ResourcesLoader.loadContent(decompiler, this);
|
return ResourcesLoader.loadContent(decompiler, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +70,21 @@ public class ResourceFile {
|
|||||||
this.zipRef = zipRef;
|
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;
|
return zipRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
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;
|
package jadx.api;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
public enum ResourceType {
|
public enum ResourceType {
|
||||||
CODE(".dex", ".class"),
|
CODE(".dex", ".jar", ".class"),
|
||||||
MANIFEST("AndroidManifest.xml"),
|
XML(".xml"),
|
||||||
XML(".xml"), // TODO binary or not?
|
ARSC(".arsc"),
|
||||||
ARSC(".arsc"), // TODO decompile !!!
|
FONT(".ttf", ".otf"),
|
||||||
FONT(".ttf"),
|
|
||||||
IMG(".png", ".gif", ".jpg"),
|
IMG(".png", ".gif", ".jpg"),
|
||||||
|
MEDIA(".mp3", ".wav"),
|
||||||
LIB(".so"),
|
LIB(".so"),
|
||||||
|
MANIFEST,
|
||||||
UNKNOWN;
|
UNKNOWN;
|
||||||
|
|
||||||
private final String[] exts;
|
private final String[] exts;
|
||||||
@@ -20,31 +27,31 @@ public enum ResourceType {
|
|||||||
return exts;
|
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 (ResourceType type : ResourceType.values()) {
|
||||||
for (String ext : type.getExts()) {
|
for (String ext : type.getExts()) {
|
||||||
if (fileName.endsWith(ext)) {
|
ResourceType prev = EXT_MAP.put(ext, type);
|
||||||
return 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;
|
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;
|
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.BufferedInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
@@ -21,118 +14,132 @@ import java.util.zip.ZipFile;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
// TODO: move to core package
|
||||||
public final class ResourcesLoader {
|
public final class ResourcesLoader {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
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;
|
private final JadxDecompiler jadxRef;
|
||||||
|
|
||||||
ResourcesLoader(JadxDecompiler jadxRef) {
|
ResourcesLoader(JadxDecompiler jadxRef) {
|
||||||
this.jadxRef = jadxRef;
|
this.jadxRef = jadxRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ResourceFile> load(List<InputFile> inputFiles) {
|
List<ResourceFile> load() {
|
||||||
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
|
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
|
||||||
for (InputFile file : inputFiles) {
|
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
||||||
loadFile(list, file.getFile());
|
for (File file : inputFiles) {
|
||||||
|
loadFile(list, file);
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ResourceDecoder {
|
public interface ResourceDecoder<T> {
|
||||||
Object decode(long size, InputStream is) throws IOException;
|
T decode(long size, InputStream is) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
|
||||||
ZipRef zipRef = rf.getZipRef();
|
|
||||||
if (zipRef == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ZipFile zipFile = null;
|
|
||||||
InputStream inputStream = null;
|
|
||||||
try {
|
try {
|
||||||
zipFile = new ZipFile(zipRef.getZipFile());
|
ZipRef zipRef = rf.getZipRef();
|
||||||
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
if (zipRef == null) {
|
||||||
if (entry == null) {
|
File file = new File(rf.getOriginalName());
|
||||||
throw new IOException("Zip entry not found: " + zipRef);
|
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||||
}
|
return decoder.decode(file.length(), inputStream);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
if (inputStream != null) {
|
} else {
|
||||||
inputStream.close();
|
try (ZipFile zipFile = new ZipFile(zipRef.getZipFile())) {
|
||||||
}
|
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
||||||
} catch (Exception e) {
|
if (entry == null) {
|
||||||
LOG.debug("Error close zip file: {}", zipRef, e);
|
throw new IOException("Zip entry not found: " + zipRef);
|
||||||
}
|
}
|
||||||
}
|
if (!ZipSecurity.isValidZipEntry(entry)) {
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
static CodeWriter loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
|
try (InputStream inputStream = ZipSecurity.getInputStreamForEntry(zipFile, entry)) {
|
||||||
try {
|
return decoder.decode(entry.getSize(), inputStream);
|
||||||
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.));
|
|
||||||
}
|
}
|
||||||
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) {
|
} catch (JadxException e) {
|
||||||
LOG.error("Decode error", e);
|
LOG.error("Decode error", e);
|
||||||
CodeWriter cw = new CodeWriter();
|
CodeWriter cw = new CodeWriter();
|
||||||
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
||||||
cw.startLine(Utils.getStackTrace(e.getCause()));
|
Utils.appendStackTrace(cw, e.getCause());
|
||||||
return cw;
|
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 {
|
InputStream inputStream) throws IOException {
|
||||||
switch (type) {
|
switch (rf.getType()) {
|
||||||
case MANIFEST:
|
case MANIFEST:
|
||||||
case XML:
|
case XML:
|
||||||
return jadxRef.getXmlParser().parse(inputStream);
|
ICodeInfo content = jadxRef.getXmlParser().parse(inputStream);
|
||||||
|
return ResContainer.textResource(rf.getDeobfName(), content);
|
||||||
|
|
||||||
case ARSC:
|
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) {
|
private void loadFile(List<ResourceFile> list, File file) {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ZipFile zip = null;
|
if (FileUtils.isZipFile(file)) {
|
||||||
try {
|
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> addEntry(list, file, entry));
|
||||||
zip = new ZipFile(file);
|
} else {
|
||||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
addResourceFile(list, file);
|
||||||
while (entries.hasMoreElements()) {
|
}
|
||||||
ZipEntry entry = entries.nextElement();
|
}
|
||||||
addEntry(list, file, entry);
|
|
||||||
}
|
private void addResourceFile(List<ResourceFile> list, File file) {
|
||||||
} catch (IOException e) {
|
String name = file.getAbsolutePath();
|
||||||
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
|
ResourceType type = ResourceType.getFileType(name);
|
||||||
} finally {
|
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||||
if (zip != null) {
|
if (rf != null) {
|
||||||
try {
|
list.add(rf);
|
||||||
zip.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Zip file close error: {}", file.getAbsolutePath(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,28 +149,16 @@ public final class ResourcesLoader {
|
|||||||
}
|
}
|
||||||
String name = entry.getName();
|
String name = entry.getName();
|
||||||
ResourceType type = ResourceType.getFileType(name);
|
ResourceType type = ResourceType.getFileType(name);
|
||||||
ResourceFile rf = new ResourceFile(jadxRef, name, type);
|
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||||
rf.setZipRef(new ZipRef(zipFile, name));
|
if (rf != null) {
|
||||||
list.add(rf);
|
rf.setZipRef(new ZipRef(zipFile, name));
|
||||||
// LOG.debug("Add resource entry: {}, size: {}", name, entry.getSize());
|
list.add(rf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
public static ICodeInfo loadToCodeWriter(InputStream is) throws IOException {
|
||||||
CodeWriter cw = new CodeWriter();
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||||
byte[] buffer = new byte[READ_BUFFER_SIZE];
|
copyStream(is, baos);
|
||||||
int count;
|
return new SimpleCodeInfo(baos.toString("UTF-8"));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 class Consts {
|
||||||
public static final boolean DEBUG = false;
|
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_OBJECT = "java.lang.Object";
|
||||||
public static final String CLASS_STRING = "java.lang.String";
|
public static final String CLASS_STRING = "java.lang.String";
|
||||||
public static final String CLASS_CLASS = "java.lang.Class";
|
public static final String CLASS_CLASS = "java.lang.Class";
|
||||||
public static final String CLASS_THROWABLE = "java.lang.Throwable";
|
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_ENUM = "java.lang.Enum";
|
||||||
|
|
||||||
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
|
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
|
||||||
|
|
||||||
public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
|
public static final String DALVIK_ANNOTATION_PKG = "Ldalvik/annotation/";
|
||||||
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
|
public static final String DALVIK_SIGNATURE = "Ldalvik/annotation/Signature;";
|
||||||
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
|
public static final String DALVIK_INNER_CLASS = "Ldalvik/annotation/InnerClass;";
|
||||||
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
|
public static final String DALVIK_THROWS = "Ldalvik/annotation/Throws;";
|
||||||
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
|
public static final String DALVIK_ANNOTATION_DEFAULT = "Ldalvik/annotation/AnnotationDefault;";
|
||||||
|
|
||||||
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
|
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
|
||||||
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
||||||
|
|
||||||
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
|
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
|
||||||
|
|
||||||
|
private Consts() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,6 @@
|
|||||||
package jadx.core;
|
package jadx.core;
|
||||||
|
|
||||||
import jadx.api.IJadxArgs;
|
import java.io.InputStream;
|
||||||
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.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
@@ -43,86 +10,166 @@ import java.util.jar.Manifest;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
public class Jadx {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||||
|
|
||||||
|
private Jadx() {
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
if (Consts.DEBUG) {
|
if (Consts.DEBUG) {
|
||||||
LOG.info("debug enabled");
|
LOG.info("debug enabled");
|
||||||
}
|
}
|
||||||
if (Jadx.class.desiredAssertionStatus()) {
|
|
||||||
LOG.info("assertions enabled");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
|
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
||||||
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
|
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()) {
|
if (args.isFallbackMode()) {
|
||||||
passes.add(new FallbackModeVisitor());
|
return getFallbackPassesList();
|
||||||
} else {
|
}
|
||||||
passes.add(new BlockSplitter());
|
|
||||||
passes.add(new BlockProcessor());
|
|
||||||
passes.add(new BlockExceptionHandler());
|
|
||||||
passes.add(new BlockFinallyExtract());
|
|
||||||
passes.add(new BlockFinish());
|
|
||||||
|
|
||||||
passes.add(new SSATransform());
|
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||||
passes.add(new DebugInfoVisitor());
|
if (args.isDebugInfo()) {
|
||||||
passes.add(new TypeInference());
|
passes.add(new DebugInfoAttachVisitor());
|
||||||
|
}
|
||||||
|
passes.add(new AttachTryCatchVisitor());
|
||||||
|
passes.add(new ProcessInstructionsVisitor());
|
||||||
|
|
||||||
if (args.isRawCFGOutput()) {
|
passes.add(new BlockSplitter());
|
||||||
passes.add(DotGraphVisitor.dumpRaw(outDir));
|
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 AttachMethodDetails());
|
||||||
passes.add(new FinishTypeInference());
|
passes.add(new OverrideMethodVisitor());
|
||||||
passes.add(new EliminatePhiNodes());
|
|
||||||
|
|
||||||
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 InlineMethods());
|
||||||
passes.add(new ReSugarCode());
|
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(new RegionMakerVisitor());
|
||||||
passes.add(DotGraphVisitor.dump(outDir));
|
passes.add(new IfRegionVisitor());
|
||||||
}
|
passes.add(new ReturnVisitor());
|
||||||
|
passes.add(new CleanRegions());
|
||||||
|
|
||||||
passes.add(new RegionMakerVisitor());
|
passes.add(new CodeShrinkVisitor());
|
||||||
passes.add(new IfRegionVisitor());
|
passes.add(new MethodInvokeVisitor());
|
||||||
passes.add(new ReturnVisitor());
|
passes.add(new SimplifyVisitor());
|
||||||
|
passes.add(new CheckRegions());
|
||||||
|
|
||||||
passes.add(new CodeShrinker());
|
passes.add(new EnumVisitor());
|
||||||
passes.add(new SimplifyVisitor());
|
passes.add(new ExtractFieldInit());
|
||||||
passes.add(new CheckRegions());
|
passes.add(new FixAccessModifiers());
|
||||||
|
passes.add(new ProcessAnonymous());
|
||||||
|
passes.add(new ClassModifier());
|
||||||
|
passes.add(new LoopRegionVisitor());
|
||||||
|
|
||||||
if (args.isCFGOutput()) {
|
passes.add(new MarkMethodsForInline());
|
||||||
passes.add(DotGraphVisitor.dumpRegions(outDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
passes.add(new MethodInlineVisitor());
|
passes.add(new ProcessVariables());
|
||||||
passes.add(new ClassModifier());
|
passes.add(new PrepareForCodeGen());
|
||||||
passes.add(new EnumVisitor());
|
if (args.isCfgOutput()) {
|
||||||
passes.add(new PrepareForCodeGen());
|
passes.add(DotGraphVisitor.dumpRegions());
|
||||||
passes.add(new LoopRegionVisitor());
|
|
||||||
passes.add(new ProcessVariables());
|
|
||||||
|
|
||||||
passes.add(new DependencyCollector());
|
|
||||||
|
|
||||||
passes.add(new RenameVisitor());
|
|
||||||
}
|
}
|
||||||
return passes;
|
return passes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getVersion() {
|
public static String getVersion() {
|
||||||
try {
|
try {
|
||||||
ClassLoader classLoader = Utils.class.getClassLoader();
|
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||||
if (classLoader != null) {
|
if (classLoader != null) {
|
||||||
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
|
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||||
while (resources.hasMoreElements()) {
|
while (resources.hasMoreElements()) {
|
||||||
Manifest manifest = new Manifest(resources.nextElement().openStream());
|
try (InputStream is = resources.nextElement().openStream()) {
|
||||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
Manifest manifest = new Manifest(is);
|
||||||
if (ver != null) {
|
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||||
return ver;
|
if (ver != null) {
|
||||||
|
return ver;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +1,97 @@
|
|||||||
package jadx.core;
|
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.codegen.CodeGen;
|
||||||
|
import jadx.core.dex.attributes.AFlag;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
|
import jadx.core.dex.nodes.LoadStage;
|
||||||
import jadx.core.dex.visitors.DepthTraversal;
|
import jadx.core.dex.visitors.DepthTraversal;
|
||||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||||
import jadx.core.utils.ErrorsCounter;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import java.util.List;
|
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
||||||
|
import static jadx.core.dex.nodes.ProcessState.LOADED;
|
||||||
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.NOT_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.PROCESS_COMPLETE;
|
||||||
import static jadx.core.dex.nodes.ProcessState.STARTED;
|
import static jadx.core.dex.nodes.ProcessState.PROCESS_STARTED;
|
||||||
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
|
|
||||||
|
|
||||||
public final class ProcessClass {
|
public final class ProcessClass {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
|
|
||||||
|
|
||||||
private ProcessClass() {
|
private ProcessClass() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
|
@Nullable
|
||||||
if (codeGen == null && cls.getState() == PROCESSED) {
|
private static ICodeInfo process(ClassNode cls, boolean codegen) {
|
||||||
return;
|
if (!codegen && cls.getState() == PROCESS_COMPLETE) {
|
||||||
|
// nothing to do
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
synchronized (cls) {
|
synchronized (cls.getClassInfo()) {
|
||||||
try {
|
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) {
|
if (cls.getState() == NOT_LOADED) {
|
||||||
cls.load();
|
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);
|
DepthTraversal.visit(visitor, cls);
|
||||||
}
|
}
|
||||||
cls.setState(PROCESSED);
|
cls.setState(PROCESS_COMPLETE);
|
||||||
}
|
}
|
||||||
if (cls.getState() == PROCESSED && codeGen != null) {
|
if (codegen) {
|
||||||
processDependencies(cls, passes);
|
ICodeInfo code = CodeGen.generate(cls);
|
||||||
codeGen.visit(cls);
|
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
|
||||||
cls.setState(GENERATED);
|
cls.unload();
|
||||||
}
|
cls.setState(GENERATED_AND_UNLOADED);
|
||||||
} catch (Exception e) {
|
}
|
||||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
return code;
|
||||||
} finally {
|
|
||||||
if (cls.getState() == GENERATED) {
|
|
||||||
cls.unload();
|
|
||||||
cls.setState(UNLOADED);
|
|
||||||
}
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
cls.addError("Class process error: " + e.getClass().getSimpleName(), e);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
@NotNull
|
||||||
for (ClassNode depCls : cls.getDependencies()) {
|
public static ICodeInfo generateCode(ClassNode cls) {
|
||||||
process(depCls, passes, null);
|
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;
|
package jadx.core.clsp;
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import java.io.BufferedInputStream;
|
||||||
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.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
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.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
* 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 CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
|
||||||
|
|
||||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
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 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);
|
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;
|
int k = 0;
|
||||||
for (ClassNode cls : list) {
|
for (ClassNode cls : list) {
|
||||||
String clsRawName = cls.getRawName();
|
ArgType clsType = cls.getClassInfo().getType();
|
||||||
if (cls.getAccessFlags().isPublic()) {
|
String clsRawName = clsType.getObject();
|
||||||
NClass nClass = new NClass(clsRawName, k);
|
cls.load();
|
||||||
if (names.put(clsRawName, nClass) != null) {
|
ClspClass nClass = new ClspClass(clsType, k);
|
||||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
if (names.put(clsRawName, nClass) != null) {
|
||||||
}
|
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||||
k++;
|
|
||||||
} else {
|
|
||||||
names.put(clsRawName, null);
|
|
||||||
}
|
}
|
||||||
|
k++;
|
||||||
|
nClass.setTypeParameters(cls.getGenericTypeParameters());
|
||||||
|
nClass.setMethods(getMethodsDetails(cls));
|
||||||
}
|
}
|
||||||
classes = new NClass[k];
|
classes = new ClspClass[k];
|
||||||
k = 0;
|
k = 0;
|
||||||
for (ClassNode cls : list) {
|
for (ClassNode cls : list) {
|
||||||
if (cls.getAccessFlags().isPublic()) {
|
ClspClass nClass = getCls(cls, names);
|
||||||
NClass nClass = getCls(cls.getRawName(), names);
|
if (nClass == null) {
|
||||||
if (nClass == null) {
|
throw new JadxRuntimeException("Missing class: " + cls);
|
||||||
throw new JadxRuntimeException("Missing class: " + cls);
|
|
||||||
}
|
|
||||||
nClass.setParents(makeParentsArray(cls, names));
|
|
||||||
classes[k] = nClass;
|
|
||||||
k++;
|
|
||||||
}
|
}
|
||||||
|
nClass.setParents(makeParentsArray(cls));
|
||||||
|
classes[k] = nClass;
|
||||||
|
k++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
private List<ClspMethod> getMethodsDetails(ClassNode cls) {
|
||||||
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
|
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();
|
ArgType superClass = cls.getSuperClass();
|
||||||
if (superClass != null) {
|
if (superClass == null) {
|
||||||
NClass c = getCls(superClass.getObject(), names);
|
// cls is java.lang.Object
|
||||||
if (c != null) {
|
return EMPTY_ARGTYPE_ARRAY;
|
||||||
parents.add(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
|
||||||
|
parents[0] = superClass;
|
||||||
|
int k = 1;
|
||||||
for (ArgType iface : cls.getInterfaces()) {
|
for (ArgType iface : cls.getInterfaces()) {
|
||||||
NClass c = getCls(iface.getObject(), names);
|
parents[k] = iface;
|
||||||
if (c != null) {
|
k++;
|
||||||
parents.add(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return parents.toArray(new NClass[parents.size()]);
|
return parents;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
private static ClspClass getCls(ClassNode cls, Map<String, ClspClass> names) {
|
||||||
NClass id = names.get(fullName);
|
return getCls(cls.getRawName(), names);
|
||||||
if (id == null && !names.containsKey(fullName)) {
|
}
|
||||||
|
|
||||||
|
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);
|
LOG.debug("Class not found: {}", fullName);
|
||||||
}
|
}
|
||||||
return id;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
void save(File output) throws IOException {
|
public void save(Path path) throws IOException {
|
||||||
FileUtils.makeDirsForFile(output);
|
FileUtils.makeDirsForFile(path);
|
||||||
|
String outputName = path.getFileName().toString();
|
||||||
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
|
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||||
try {
|
try (BufferedOutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(path))) {
|
||||||
String outputName = output.getName();
|
|
||||||
if (outputName.endsWith(CLST_EXTENSION)) {
|
|
||||||
save(outputStream);
|
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 {
|
} else if (outputName.endsWith(".jar")) {
|
||||||
outputStream.close();
|
Path temp = FileUtils.createTempFile(".zip");
|
||||||
}
|
Files.copy(path, temp, StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
|
||||||
|
|
||||||
public void save(OutputStream output) throws IOException {
|
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
|
||||||
DataOutputStream out = new DataOutputStream(output);
|
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
|
||||||
try {
|
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
|
||||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
boolean clstReplaced = false;
|
||||||
out.writeByte(VERSION);
|
ZipEntry entry = in.getNextEntry();
|
||||||
|
while (entry != null) {
|
||||||
LOG.info("Classes count: {}", classes.length);
|
String entryName = entry.getName();
|
||||||
out.writeInt(classes.length);
|
ZipEntry copyEntry = new ZipEntry(entryName);
|
||||||
for (NClass cls : classes) {
|
copyEntry.setLastModifiedTime(entry.getLastModifiedTime()); // preserve modified time
|
||||||
writeString(out, cls.getName());
|
out.putNextEntry(copyEntry);
|
||||||
}
|
if (entryName.equals(clst)) {
|
||||||
for (NClass cls : classes) {
|
save(out);
|
||||||
NClass[] parents = cls.getParents();
|
clstReplaced = true;
|
||||||
out.writeByte(parents.length);
|
} else {
|
||||||
for (NClass parent : parents) {
|
FileUtils.copyStream(in, out);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
} finally {
|
entry = in.getNextEntry();
|
||||||
in.close();
|
}
|
||||||
|
if (!clstReplaced) {
|
||||||
|
out.putNextEntry(new ZipEntry(clst));
|
||||||
|
save(out);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} else {
|
||||||
inputStream.close();
|
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load(InputStream input) throws IOException, DecodeException {
|
private void save(OutputStream output) throws IOException {
|
||||||
DataInputStream in = new DataInputStream(input);
|
DataOutputStream out = new DataOutputStream(output);
|
||||||
try {
|
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()];
|
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||||
int readHeaderLength = in.read(header);
|
int readHeaderLength = in.read(header);
|
||||||
int version = in.readByte();
|
int version = in.readByte();
|
||||||
@@ -198,33 +354,142 @@ public class ClsSet {
|
|||||||
|| version != VERSION) {
|
|| version != VERSION) {
|
||||||
throw new DecodeException("Wrong jadx class set header");
|
throw new DecodeException("Wrong jadx class set header");
|
||||||
}
|
}
|
||||||
int count = in.readInt();
|
int clsCount = in.readInt();
|
||||||
classes = new NClass[count];
|
classes = new ClspClass[clsCount];
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < clsCount; i++) {
|
||||||
String name = readString(in);
|
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++) {
|
for (int i = 0; i < clsCount; i++) {
|
||||||
int pCount = in.readByte();
|
ClspClass nClass = classes[i];
|
||||||
NClass[] parents = new NClass[pCount];
|
ClassInfo clsInfo = ClassInfo.fromType(root, nClass.getClsType());
|
||||||
for (int j = 0; j < pCount; j++) {
|
nClass.setParents(readArgTypesArray(in));
|
||||||
parents[j] = classes[in.readInt()];
|
nClass.setTypeParameters(readArgTypesList(in));
|
||||||
}
|
nClass.setMethods(readClsMethods(in, clsInfo));
|
||||||
classes[i].setParents(parents);
|
|
||||||
}
|
}
|
||||||
} 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);
|
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);
|
out.write(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String readString(DataInputStream in) throws IOException {
|
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];
|
byte[] bytes = new byte[len];
|
||||||
int count = in.read(bytes);
|
int count = in.read(bytes);
|
||||||
while (count != len) {
|
while (count != len) {
|
||||||
@@ -238,12 +503,23 @@ public class ClsSet {
|
|||||||
return new String(bytes, STRING_CHARSET);
|
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() {
|
public int getClassesCount() {
|
||||||
return classes.length;
|
return classes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addToMap(Map<String, NClass> nameMap) {
|
public void addToMap(Map<String, ClspClass> nameMap) {
|
||||||
for (NClass cls : classes) {
|
for (ClspClass cls : classes) {
|
||||||
nameMap.put(cls.getName(), cls);
|
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;
|
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.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -13,27 +10,44 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
public class ClspGraph {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||||
|
|
||||||
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
|
private final RootNode root;
|
||||||
private Map<String, NClass> nameMap;
|
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 {
|
public void load() throws IOException, DecodeException {
|
||||||
ClsSet set = new ClsSet();
|
ClsSet set = new ClsSet(root);
|
||||||
set.load();
|
set.loadFromClstFile();
|
||||||
addClasspath(set);
|
addClasspath(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addClasspath(ClsSet set) {
|
public void addClasspath(ClsSet set) {
|
||||||
if (nameMap == null) {
|
if (nameMap == null) {
|
||||||
nameMap = new HashMap<String, NClass>(set.getClassesCount());
|
nameMap = new HashMap<>(set.getClassesCount());
|
||||||
set.addToMap(nameMap);
|
set.addToMap(nameMap);
|
||||||
} else {
|
} else {
|
||||||
throw new JadxRuntimeException("Classpath already loaded");
|
throw new JadxRuntimeException("Classpath already loaded");
|
||||||
@@ -44,82 +58,170 @@ public class ClspGraph {
|
|||||||
if (nameMap == null) {
|
if (nameMap == null) {
|
||||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||||
}
|
}
|
||||||
int size = classes.size();
|
|
||||||
NClass[] nClasses = new NClass[size];
|
|
||||||
int k = 0;
|
|
||||||
for (ClassNode cls : classes) {
|
for (ClassNode cls : classes) {
|
||||||
nClasses[k++] = addClass(cls);
|
addClass(cls);
|
||||||
}
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NClass addClass(ClassNode cls) {
|
public boolean isClsKnown(String fullName) {
|
||||||
String rawName = cls.getRawName();
|
return nameMap.containsKey(fullName);
|
||||||
NClass nClass = new NClass(rawName, -1);
|
|
||||||
nameMap.put(rawName, nClass);
|
|
||||||
return nClass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
public boolean isImplements(String clsName, String implClsName) {
|
||||||
Set<String> anc = getAncestors(clsName);
|
Set<String> anc = getSuperTypes(clsName);
|
||||||
return anc.contains(implClsName);
|
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) {
|
public String getCommonAncestor(String clsName, String implClsName) {
|
||||||
if (clsName.equals(implClsName)) {
|
if (clsName.equals(implClsName)) {
|
||||||
return clsName;
|
return clsName;
|
||||||
}
|
}
|
||||||
NClass cls = nameMap.get(implClsName);
|
ClspClass cls = nameMap.get(implClsName);
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
LOG.debug("Missing class: {}", implClsName);
|
missingClasses.add(clsName);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (isImplements(clsName, implClsName)) {
|
if (isImplements(clsName, implClsName)) {
|
||||||
return implClsName;
|
return implClsName;
|
||||||
}
|
}
|
||||||
Set<String> anc = getAncestors(clsName);
|
Set<String> anc = getSuperTypes(clsName);
|
||||||
return searchCommonParent(anc, cls);
|
return searchCommonParent(anc, cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String searchCommonParent(Set<String> anc, NClass cls) {
|
private String searchCommonParent(Set<String> anc, ClspClass cls) {
|
||||||
for (NClass p : cls.getParents()) {
|
for (ArgType p : cls.getParents()) {
|
||||||
String name = p.getName();
|
String name = p.getObject();
|
||||||
if (anc.contains(name)) {
|
if (anc.contains(name)) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
String r = searchCommonParent(anc, p);
|
ClspClass nCls = getClspClass(p);
|
||||||
if (r != null) {
|
if (nCls != null) {
|
||||||
return r;
|
String r = searchCommonParent(anc, nCls);
|
||||||
|
if (r != null) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> getAncestors(String clsName) {
|
public Set<String> getSuperTypes(String clsName) {
|
||||||
Set<String> result = ancestorCache.get(clsName);
|
Set<String> fromCache = superTypesCache.get(clsName);
|
||||||
if (result != null) {
|
if (fromCache != null) {
|
||||||
return result;
|
return fromCache;
|
||||||
}
|
}
|
||||||
NClass cls = nameMap.get(clsName);
|
ClspClass cls = nameMap.get(clsName);
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
LOG.debug("Missing class: {}", clsName);
|
missingClasses.add(clsName);
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
result = new HashSet<String>();
|
Set<String> result = new HashSet<>();
|
||||||
addAncestorsNames(cls, result);
|
addSuperTypes(cls, result);
|
||||||
|
return putInSuperTypesCache(clsName, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
|
||||||
if (result.isEmpty()) {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAncestorsNames(NClass cls, Set<String> result) {
|
private void addSuperTypes(ClspClass cls, Set<String> result) {
|
||||||
result.add(cls.getName());
|
for (ArgType parentType : cls.getParents()) {
|
||||||
for (NClass p : cls.getParents()) {
|
if (parentType == null) {
|
||||||
addAncestorsNames(p, result);
|
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;
|
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.Consts;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.IAttributeNode;
|
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.AnnotationsList;
|
||||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
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.ClassNode;
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
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 {
|
public class AnnotationGen {
|
||||||
|
|
||||||
private final ClassNode cls;
|
private final ClassNode cls;
|
||||||
@@ -50,7 +55,7 @@ public class AnnotationGen {
|
|||||||
if (aList == null || aList.isEmpty()) {
|
if (aList == null || aList.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (Annotation a : aList.getAll()) {
|
for (IAnnotation a : aList.getAll()) {
|
||||||
formatAnnotation(code, a);
|
formatAnnotation(code, a);
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
}
|
}
|
||||||
@@ -61,50 +66,61 @@ public class AnnotationGen {
|
|||||||
if (aList == null || aList.isEmpty()) {
|
if (aList == null || aList.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (Annotation a : aList.getAll()) {
|
for (IAnnotation a : aList.getAll()) {
|
||||||
String aCls = a.getAnnotationClass();
|
String aCls = a.getAnnotationClass();
|
||||||
if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
||||||
// skip
|
|
||||||
if (Consts.DEBUG) {
|
|
||||||
code.startLine("// " + a);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
code.startLine();
|
code.startLine();
|
||||||
formatAnnotation(code, a);
|
formatAnnotation(code, a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void formatAnnotation(CodeWriter code, Annotation a) {
|
private void formatAnnotation(CodeWriter code, IAnnotation a) {
|
||||||
code.add('@');
|
code.add('@');
|
||||||
classGen.useType(code, a.getType());
|
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
|
||||||
Map<String, Object> vl = a.getValues();
|
if (annCls != null) {
|
||||||
|
classGen.useClass(code, annCls);
|
||||||
|
} else {
|
||||||
|
classGen.useClass(code, a.getAnnotationClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, EncodedValue> vl = a.getValues();
|
||||||
if (!vl.isEmpty()) {
|
if (!vl.isEmpty()) {
|
||||||
code.add('(');
|
code.add('(');
|
||||||
if (vl.size() == 1 && vl.containsKey("value")) {
|
for (Iterator<Entry<String, EncodedValue>> it = vl.entrySet().iterator(); it.hasNext();) {
|
||||||
encodeValue(code, vl.get("value"));
|
Entry<String, EncodedValue> e = it.next();
|
||||||
} else {
|
String paramName = getParamName(annCls, e.getKey());
|
||||||
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
|
if (paramName.equals("value") && vl.size() == 1) {
|
||||||
Entry<String, Object> e = it.next();
|
// don't add "value = " if no other parameters
|
||||||
code.add(e.getKey());
|
} else {
|
||||||
|
code.add(paramName);
|
||||||
code.add(" = ");
|
code.add(" = ");
|
||||||
encodeValue(code, e.getValue());
|
}
|
||||||
if (it.hasNext()) {
|
encodeValue(cls.root(), code, e.getValue());
|
||||||
code.add(", ");
|
if (it.hasNext()) {
|
||||||
}
|
code.add(", ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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) {
|
public void addThrows(MethodNode mth, CodeWriter code) {
|
||||||
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
|
List<ArgType> throwList = mth.getThrows();
|
||||||
if (an != null) {
|
if (!throwList.isEmpty()) {
|
||||||
Object exs = an.getDefaultValue();
|
|
||||||
code.add(" throws ");
|
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();
|
ArgType ex = it.next();
|
||||||
classGen.useType(code, ex);
|
classGen.useType(code, ex);
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
@@ -114,62 +130,97 @@ public class AnnotationGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getAnnotationDefaultValue(String name) {
|
public EncodedValue getAnnotationDefaultValue(String name) {
|
||||||
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
||||||
if (an != null) {
|
if (an != null) {
|
||||||
Annotation defAnnotation = (Annotation) an.getDefaultValue();
|
EncodedValue defValue = an.getDefaultValue();
|
||||||
return defAnnotation.getValues().get(name);
|
if (defValue != null) {
|
||||||
|
IAnnotation defAnnotation = (IAnnotation) defValue.getValue();
|
||||||
|
return defAnnotation.getValues().get(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor this boilerplate code
|
// TODO: refactor this boilerplate code
|
||||||
public void encodeValue(CodeWriter code, Object val) {
|
public void encodeValue(RootNode root, CodeWriter code, EncodedValue encodedValue) {
|
||||||
if (val == null) {
|
if (encodedValue == null) {
|
||||||
code.add("null");
|
code.add("null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (val instanceof String) {
|
Object value = encodedValue.getValue();
|
||||||
code.add(StringUtils.unescapeString((String) val));
|
switch (encodedValue.getType()) {
|
||||||
} else if (val instanceof Integer) {
|
case ENCODED_NULL:
|
||||||
code.add(TypeGen.formatInteger((Integer) val));
|
code.add("null");
|
||||||
} else if (val instanceof Character) {
|
break;
|
||||||
code.add(StringUtils.unescapeChar((Character) val));
|
case ENCODED_BOOLEAN:
|
||||||
} else if (val instanceof Boolean) {
|
code.add(Boolean.TRUE.equals(value) ? "true" : "false");
|
||||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
break;
|
||||||
} else if (val instanceof Float) {
|
case ENCODED_BYTE:
|
||||||
code.add(TypeGen.formatFloat((Float) val));
|
code.add(TypeGen.formatByte((Byte) value, false));
|
||||||
} else if (val instanceof Double) {
|
break;
|
||||||
code.add(TypeGen.formatDouble((Double) val));
|
case ENCODED_SHORT:
|
||||||
} else if (val instanceof Long) {
|
code.add(TypeGen.formatShort((Short) value, false));
|
||||||
code.add(TypeGen.formatLong((Long) val));
|
break;
|
||||||
} else if (val instanceof Short) {
|
case ENCODED_CHAR:
|
||||||
code.add(TypeGen.formatShort((Short) val));
|
code.add(getStringUtils().unescapeChar((Character) value));
|
||||||
} else if (val instanceof Byte) {
|
break;
|
||||||
code.add(TypeGen.formatByte((Byte) val));
|
case ENCODED_INT:
|
||||||
} else if (val instanceof ArgType) {
|
code.add(TypeGen.formatInteger((Integer) value, false));
|
||||||
classGen.useType(code, (ArgType) val);
|
break;
|
||||||
code.add(".class");
|
case ENCODED_LONG:
|
||||||
} else if (val instanceof FieldInfo) {
|
code.add(TypeGen.formatLong((Long) value, false));
|
||||||
// must be a static field
|
break;
|
||||||
FieldInfo field = (FieldInfo) val;
|
case ENCODED_FLOAT:
|
||||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
code.add(TypeGen.formatFloat((Float) value));
|
||||||
} else if (val instanceof Iterable) {
|
break;
|
||||||
code.add('{');
|
case ENCODED_DOUBLE:
|
||||||
Iterator<?> it = ((Iterable) val).iterator();
|
code.add(TypeGen.formatDouble((Double) value));
|
||||||
while (it.hasNext()) {
|
break;
|
||||||
Object obj = it.next();
|
case ENCODED_STRING:
|
||||||
encodeValue(code, obj);
|
code.add(getStringUtils().unescapeString((String) value));
|
||||||
if (it.hasNext()) {
|
break;
|
||||||
code.add(", ");
|
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());
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
code.add('}');
|
case ENCODED_METHOD:
|
||||||
} else if (val instanceof Annotation) {
|
// TODO
|
||||||
formatAnnotation(code, (Annotation) val);
|
break;
|
||||||
} else {
|
case ENCODED_ARRAY:
|
||||||
// TODO: also can be method values
|
code.add('{');
|
||||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
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;
|
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.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.AttrNode;
|
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;
|
||||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
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.AccessInfo;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
import jadx.core.dex.nodes.ClassNode;
|
||||||
import jadx.core.dex.nodes.DexNode;
|
|
||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
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.ErrorsCounter;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
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;
|
|
||||||
|
|
||||||
public class ClassGen {
|
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 ClassNode cls;
|
||||||
private final ClassGen parentGen;
|
private final ClassGen parentGen;
|
||||||
private final AnnotationGen annotationGen;
|
private final AnnotationGen annotationGen;
|
||||||
private final boolean fallback;
|
private final boolean fallback;
|
||||||
|
private final boolean useImports;
|
||||||
private final boolean showInconsistentCode;
|
private final boolean showInconsistentCode;
|
||||||
|
|
||||||
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
|
private final Set<ClassInfo> imports = new HashSet<>();
|
||||||
private int clsDeclLine;
|
private int clsDeclLine;
|
||||||
|
|
||||||
public ClassGen(ClassNode cls, IJadxArgs jadxArgs) {
|
private boolean bodyGenStarted;
|
||||||
this(cls, null, jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
|
||||||
|
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
|
||||||
|
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClassGen(ClassNode cls, ClassGen parentClsGen) {
|
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.cls = cls;
|
||||||
this.parentGen = parentClsGen;
|
this.parentGen = parentClsGen;
|
||||||
this.fallback = fallback;
|
this.fallback = fallback;
|
||||||
|
this.useImports = useImports;
|
||||||
this.showInconsistentCode = showBadCode;
|
this.showInconsistentCode = showBadCode;
|
||||||
|
|
||||||
this.annotationGen = new AnnotationGen(cls, this);
|
this.annotationGen = new AnnotationGen(cls, this);
|
||||||
@@ -76,7 +81,7 @@ public class ClassGen {
|
|||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CodeWriter makeClass() throws CodegenException {
|
public ICodeInfo makeClass() throws CodegenException {
|
||||||
CodeWriter clsBody = new CodeWriter();
|
CodeWriter clsBody = new CodeWriter();
|
||||||
addClassCode(clsBody);
|
addClassCode(clsBody);
|
||||||
|
|
||||||
@@ -87,31 +92,33 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
int importsCount = imports.size();
|
int importsCount = imports.size();
|
||||||
if (importsCount != 0) {
|
if (importsCount != 0) {
|
||||||
List<String> sortImports = new ArrayList<String>(importsCount);
|
List<ClassInfo> sortedImports = new ArrayList<>(imports);
|
||||||
for (ClassInfo ic : imports) {
|
sortedImports.sort(Comparator.comparing(ClassInfo::getAliasFullName));
|
||||||
sortImports.add(ic.getAlias().getFullName());
|
sortedImports.forEach(classInfo -> {
|
||||||
}
|
clsCode.startLine("import ");
|
||||||
Collections.sort(sortImports);
|
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||||
|
if (classNode != null) {
|
||||||
for (String imp : sortImports) {
|
clsCode.attachAnnotation(classNode);
|
||||||
clsCode.startLine("import ").add(imp).add(';');
|
}
|
||||||
}
|
clsCode.add(classInfo.getAliasFullName());
|
||||||
|
clsCode.add(';');
|
||||||
|
});
|
||||||
clsCode.newLine();
|
clsCode.newLine();
|
||||||
|
|
||||||
sortImports.clear();
|
|
||||||
imports.clear();
|
imports.clear();
|
||||||
}
|
}
|
||||||
clsCode.add(clsBody);
|
clsCode.add(clsBody);
|
||||||
return clsCode;
|
return clsCode.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addClassCode(CodeWriter code) throws CodegenException {
|
public void addClassCode(CodeWriter code) throws CodegenException {
|
||||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
|
if (Consts.DEBUG_USAGE) {
|
||||||
code.startLine("// jadx: inconsistent code");
|
addClassUsageInfo(code, cls);
|
||||||
}
|
}
|
||||||
|
CodeGenUtils.addComments(code, cls);
|
||||||
|
insertDecompilationProblems(code, cls);
|
||||||
addClassDeclaration(code);
|
addClassDeclaration(code);
|
||||||
addClassBody(code);
|
addClassBody(code);
|
||||||
}
|
}
|
||||||
@@ -119,23 +126,24 @@ public class ClassGen {
|
|||||||
public void addClassDeclaration(CodeWriter clsCode) {
|
public void addClassDeclaration(CodeWriter clsCode) {
|
||||||
AccessInfo af = cls.getAccessFlags();
|
AccessInfo af = cls.getAccessFlags();
|
||||||
if (af.isInterface()) {
|
if (af.isInterface()) {
|
||||||
af = af.remove(AccessFlags.ACC_ABSTRACT)
|
af = af.remove(AccessFlags.ABSTRACT)
|
||||||
.remove(AccessFlags.ACC_STATIC);
|
.remove(AccessFlags.STATIC);
|
||||||
} else if (af.isEnum()) {
|
} else if (af.isEnum()) {
|
||||||
af = af.remove(AccessFlags.ACC_FINAL)
|
af = af.remove(AccessFlags.FINAL)
|
||||||
.remove(AccessFlags.ACC_ABSTRACT)
|
.remove(AccessFlags.ABSTRACT)
|
||||||
.remove(AccessFlags.ACC_STATIC);
|
.remove(AccessFlags.STATIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
||||||
if (!cls.getAlias().isInner()) {
|
if (!cls.getClassInfo().isInner()) {
|
||||||
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
|
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationGen.addForClass(clsCode);
|
annotationGen.addForClass(clsCode);
|
||||||
insertSourceFileInfo(clsCode, cls);
|
|
||||||
insertRenameInfo(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.isInterface()) {
|
||||||
if (af.isAnnotation()) {
|
if (af.isAnnotation()) {
|
||||||
clsCode.add('@');
|
clsCode.add('@');
|
||||||
@@ -146,15 +154,16 @@ public class ClassGen {
|
|||||||
} else {
|
} else {
|
||||||
clsCode.add("class ");
|
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(' ');
|
clsCode.add(' ');
|
||||||
|
|
||||||
ArgType sup = cls.getSuperClass();
|
ArgType sup = cls.getSuperClass();
|
||||||
if (sup != null
|
if (sup != null
|
||||||
&& !sup.equals(ArgType.OBJECT)
|
&& !sup.equals(ArgType.OBJECT)
|
||||||
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
|
&& !cls.isEnum()) {
|
||||||
clsCode.add("extends ");
|
clsCode.add("extends ");
|
||||||
useClass(clsCode, sup);
|
useClass(clsCode, sup);
|
||||||
clsCode.add(' ');
|
clsCode.add(' ');
|
||||||
@@ -166,7 +175,7 @@ public class ClassGen {
|
|||||||
} else {
|
} else {
|
||||||
clsCode.add("implements ");
|
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();
|
ArgType interf = it.next();
|
||||||
useClass(clsCode, interf);
|
useClass(clsCode, interf);
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
@@ -177,34 +186,37 @@ public class ClassGen {
|
|||||||
clsCode.add(' ');
|
clsCode.add(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clsCode.attachDefinition(cls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
|
public boolean addGenericTypeParameters(CodeWriter code, List<ArgType> generics, boolean classDeclaration) {
|
||||||
if (gmap == null || gmap.isEmpty()) {
|
if (generics == null || generics.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
code.add('<');
|
code.add('<');
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
|
for (ArgType genericInfo : generics) {
|
||||||
ArgType type = e.getKey();
|
|
||||||
List<ArgType> list = e.getValue();
|
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
code.add(", ");
|
code.add(", ");
|
||||||
}
|
}
|
||||||
if (type.isGenericType()) {
|
if (genericInfo.isGenericType()) {
|
||||||
code.add(type.getObject());
|
code.add(genericInfo.getObject());
|
||||||
} else {
|
} else {
|
||||||
useClass(code, type);
|
useClass(code, genericInfo);
|
||||||
}
|
}
|
||||||
|
List<ArgType> list = genericInfo.getExtendTypes();
|
||||||
if (list != null && !list.isEmpty()) {
|
if (list != null && !list.isEmpty()) {
|
||||||
code.add(" extends ");
|
code.add(" extends ");
|
||||||
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
|
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
|
||||||
ArgType g = it.next();
|
ArgType g = it.next();
|
||||||
if (g.isGenericType()) {
|
if (g.isGenericType()) {
|
||||||
code.add(g.getObject());
|
code.add(g.getObject());
|
||||||
} else {
|
} else {
|
||||||
useClass(code, g);
|
useClass(code, g);
|
||||||
|
if (classDeclaration
|
||||||
|
&& !cls.getClassInfo().isInner()
|
||||||
|
&& cls.root().getArgs().isUseImports()) {
|
||||||
|
addImport(ClassInfo.fromType(cls.root(), g));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
code.add(" & ");
|
code.add(" & ");
|
||||||
@@ -218,26 +230,50 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
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('{');
|
clsCode.add('{');
|
||||||
|
setBodyGenStarted(true);
|
||||||
clsDeclLine = clsCode.getLine();
|
clsDeclLine = clsCode.getLine();
|
||||||
clsCode.incIndent();
|
clsCode.incIndent();
|
||||||
|
if (printClassName) {
|
||||||
|
clsCode.startLine();
|
||||||
|
clsCode.add("/* class " + cls.getFullName() + " */");
|
||||||
|
}
|
||||||
addFields(clsCode);
|
addFields(clsCode);
|
||||||
addInnerClasses(clsCode, cls);
|
addInnerClsAndMethods(clsCode);
|
||||||
addMethods(clsCode);
|
|
||||||
clsCode.decIndent();
|
clsCode.decIndent();
|
||||||
clsCode.startLine('}');
|
clsCode.startLine('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
|
private void addInnerClsAndMethods(CodeWriter clsCode) {
|
||||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
Stream.of(cls.getInnerClasses(), cls.getMethods())
|
||||||
if (innerCls.contains(AFlag.DONT_GENERATE)
|
.flatMap(Collection::stream)
|
||||||
|| innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
.filter(node -> !node.contains(AFlag.DONT_GENERATE))
|
||||||
continue;
|
.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());
|
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
||||||
code.newLine();
|
code.newLine();
|
||||||
inClGen.addClassCode(code);
|
inClGen.addClassCode(code);
|
||||||
imports.addAll(inClGen.getImports());
|
imports.addAll(inClGen.getImports());
|
||||||
|
} catch (Exception e) {
|
||||||
|
innerCls.addError("Inner class code generation error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,28 +286,24 @@ public class ClassGen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMethods(CodeWriter code) {
|
private void addMethod(CodeWriter code, MethodNode mth) {
|
||||||
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
|
if (code.getLine() != clsDeclLine) {
|
||||||
for (MethodNode mth : methods) {
|
code.newLine();
|
||||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
}
|
||||||
continue;
|
int savedIndent = code.getIndent();
|
||||||
}
|
try {
|
||||||
if (code.getLine() != clsDeclLine) {
|
addMethodCode(code, mth);
|
||||||
code.newLine();
|
} catch (Exception e) {
|
||||||
}
|
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||||
try {
|
throw new JadxRuntimeException("Method generation error", e);
|
||||||
addMethod(code, mth);
|
}
|
||||||
} catch (Exception e) {
|
code.newLine().add("/*");
|
||||||
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
|
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e));
|
||||||
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(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() {
|
private boolean isMethodsPresents() {
|
||||||
@@ -283,31 +315,21 @@ public class ClassGen {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
|
public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||||
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
CodeGenUtils.addComments(code, mth);
|
||||||
|
if (mth.isNoCode()) {
|
||||||
MethodGen mthGen = new MethodGen(this, mth);
|
MethodGen mthGen = new MethodGen(this, mth);
|
||||||
mthGen.addDefinition(code);
|
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(';');
|
code.add(';');
|
||||||
} else {
|
} else {
|
||||||
|
insertDecompilationProblems(code, mth);
|
||||||
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
||||||
if (badCode) {
|
if (badCode && showInconsistentCode) {
|
||||||
code.startLine("/* JADX WARNING: inconsistent code. */");
|
mth.remove(AFlag.INCONSISTENT_CODE);
|
||||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
badCode = false;
|
||||||
ErrorsCounter.methodError(mth, "Inconsistent code");
|
|
||||||
if (showInconsistentCode) {
|
|
||||||
mth.remove(AFlag.INCONSISTENT_CODE);
|
|
||||||
badCode = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
MethodGen mthGen;
|
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);
|
mthGen = MethodGen.getFallbackMethodGen(mth);
|
||||||
} else {
|
} else {
|
||||||
mthGen = new MethodGen(this, mth);
|
mthGen = new MethodGen(this, mth);
|
||||||
@@ -317,42 +339,77 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
code.add('{');
|
code.add('{');
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
insertSourceFileInfo(code, mth);
|
mthGen.addInstructions(code);
|
||||||
if (fallback) {
|
|
||||||
mthGen.addFallbackMethodCode(code);
|
|
||||||
} else {
|
|
||||||
mthGen.addInstructions(code);
|
|
||||||
}
|
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine('}');
|
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 {
|
private void addFields(CodeWriter code) throws CodegenException {
|
||||||
addEnumFields(code);
|
addEnumFields(code);
|
||||||
for (FieldNode f : cls.getFields()) {
|
for (FieldNode f : cls.getFields()) {
|
||||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
addField(code, f);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
private boolean isFieldsPresents() {
|
||||||
for (FieldNode field : cls.getFields()) {
|
for (FieldNode field : cls.getFields()) {
|
||||||
if (!field.contains(AFlag.DONT_GENERATE)) {
|
if (!field.contains(AFlag.DONT_GENERATE)) {
|
||||||
@@ -368,17 +425,17 @@ public class ClassGen {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
InsnGen igen = null;
|
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();
|
EnumField f = it.next();
|
||||||
code.startLine(f.getField().getAlias());
|
code.startLine(f.getField().getAlias());
|
||||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
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) {
|
if (igen == null) {
|
||||||
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
|
igen = makeInsnGen(enumFields.getStaticMethod());
|
||||||
igen = new InsnGen(mthGen, false);
|
|
||||||
}
|
}
|
||||||
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
|
igen.generateMethodArguments(code, constrInsn, 0, callMth);
|
||||||
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
|
|
||||||
}
|
}
|
||||||
if (f.getCls() != null) {
|
if (f.getCls() != null) {
|
||||||
code.add(' ');
|
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) {
|
public void useType(CodeWriter code, ArgType type) {
|
||||||
PrimitiveType stype = type.getPrimitiveType();
|
PrimitiveType stype = type.getPrimitiveType();
|
||||||
if (stype == null) {
|
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) {
|
public void useClass(CodeWriter code, ArgType type) {
|
||||||
useClass(code, ClassInfo.extCls(cls.dex(), type));
|
ArgType outerType = type.getOuterType();
|
||||||
ArgType[] generics = type.getGenericTypes();
|
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) {
|
if (generics != null) {
|
||||||
code.add('<');
|
code.add('<');
|
||||||
int len = generics.length;
|
int len = generics.size();
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
code.add(", ");
|
code.add(", ");
|
||||||
}
|
}
|
||||||
ArgType gt = generics[i];
|
ArgType gt = generics.get(i);
|
||||||
ArgType wt = gt.getWildcardType();
|
ArgType wt = gt.getWildcardType();
|
||||||
if (wt != null) {
|
if (wt != null) {
|
||||||
code.add('?');
|
ArgType.WildcardBound bound = gt.getWildcardBound();
|
||||||
int bounds = gt.getWildcardBounds();
|
code.add(bound.getStr());
|
||||||
if (bounds != 0) {
|
if (bound != ArgType.WildcardBound.UNBOUND) {
|
||||||
code.add(bounds == -1 ? " super " : " extends ");
|
|
||||||
useType(code, wt);
|
useType(code, wt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -444,27 +536,49 @@ public class ClassGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
private void useClassShortName(CodeWriter code, String object) {
|
||||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
ClassInfo classInfo = ClassInfo.fromName(cls.root(), object);
|
||||||
|
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||||
if (classNode != null) {
|
if (classNode != null) {
|
||||||
code.attachAnnotation(classNode);
|
code.attachAnnotation(classNode);
|
||||||
}
|
}
|
||||||
String baseClass = useClassInternal(cls.getAlias(), classInfo.getAlias());
|
code.add(classInfo.getAliasShortName());
|
||||||
code.add(baseClass);
|
}
|
||||||
|
|
||||||
|
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) {
|
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||||
String fullName = extClsInfo.getFullName();
|
String fullName = extClsInfo.getAliasFullName();
|
||||||
if (fallback) {
|
if (fallback || !useImports) {
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
String shortName = extClsInfo.getShortName();
|
String shortName = extClsInfo.getAliasShortName();
|
||||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
if (isClassInnerFor(useCls, extClsInfo)) {
|
if (isClassInnerFor(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
|
if (extClsInfo.isInner()) {
|
||||||
|
return expandInnerClassName(useCls, extClsInfo);
|
||||||
|
}
|
||||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
@@ -472,23 +586,22 @@ public class ClassGen {
|
|||||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
// don't add import if class not public (must be accessed using inheritance)
|
if (searchCollision(cls.root(), useCls, extClsInfo)) {
|
||||||
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
|
|
||||||
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
|
||||||
return shortName;
|
|
||||||
}
|
|
||||||
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
|
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
|
// ignore classes from default package
|
||||||
fullName = extClsInfo.getNameWithoutPackage();
|
if (extClsInfo.isDefaultPackage()) {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
if (extClsInfo.getAliasPkg().equals(useCls.getAliasPkg())) {
|
||||||
|
fullName = extClsInfo.getAliasNameWithoutPackage();
|
||||||
}
|
}
|
||||||
for (ClassInfo importCls : getImports()) {
|
for (ClassInfo importCls : getImports()) {
|
||||||
if (!importCls.equals(extClsInfo)
|
if (!importCls.equals(extClsInfo)
|
||||||
&& importCls.getShortName().equals(shortName)) {
|
&& importCls.getAliasShortName().equals(shortName)) {
|
||||||
if (extClsInfo.isInner()) {
|
if (extClsInfo.isInner()) {
|
||||||
String parent = useClassInternal(useCls, extClsInfo.getParentClass().getAlias());
|
String parent = useClassInternal(useCls, extClsInfo.getParentClass());
|
||||||
return parent + "." + shortName;
|
return parent + '.' + shortName;
|
||||||
} else {
|
} else {
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
@@ -498,15 +611,35 @@ public class ClassGen {
|
|||||||
return shortName;
|
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) {
|
private void addImport(ClassInfo classInfo) {
|
||||||
if (parentGen != null) {
|
if (parentGen != null) {
|
||||||
parentGen.addImport(classInfo.getAlias());
|
parentGen.addImport(classInfo);
|
||||||
} else {
|
} else {
|
||||||
imports.add(classInfo);
|
imports.add(classInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<ClassInfo> getImports() {
|
public Set<ClassInfo> getImports() {
|
||||||
if (parentGen != null) {
|
if (parentGen != null) {
|
||||||
return parentGen.getImports();
|
return parentGen.getImports();
|
||||||
} else {
|
} else {
|
||||||
@@ -527,42 +660,69 @@ public class ClassGen {
|
|||||||
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
|
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
|
||||||
if (inner.isInner()) {
|
if (inner.isInner()) {
|
||||||
ClassInfo p = inner.getParentClass();
|
ClassInfo p = inner.getParentClass();
|
||||||
return p.equals(parent) || isClassInnerFor(p, parent);
|
return Objects.equals(p, parent) || isClassInnerFor(p, parent);
|
||||||
}
|
}
|
||||||
return false;
|
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) {
|
if (useCls == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String shortName = searchCls.getShortName();
|
String shortName = searchCls.getAliasShortName();
|
||||||
if (useCls.getShortName().equals(shortName)) {
|
if (useCls.getAliasShortName().equals(shortName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
ClassNode classNode = dex.resolveClass(useCls);
|
ClassNode classNode = root.resolveClass(useCls);
|
||||||
if (classNode != null) {
|
if (classNode != null) {
|
||||||
for (ClassNode inner : classNode.getInnerClasses()) {
|
for (ClassNode inner : classNode.getInnerClasses()) {
|
||||||
if (inner.getShortName().equals(shortName)
|
if (inner.getShortName().equals(shortName)
|
||||||
&& !inner.getAlias().equals(searchCls)) {
|
&& !inner.getFullName().equals(searchCls.getAliasFullName())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return searchCollision(dex, useCls.getParentClass(), searchCls);
|
return searchCollision(root, 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(" */");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||||
ClassInfo classInfo = cls.getClassInfo();
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
if (classInfo.isRenamed()) {
|
if (classInfo.hasAlias()) {
|
||||||
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
|
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() {
|
public boolean isFallbackMode() {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBodyGenStarted() {
|
||||||
|
return bodyGenStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBodyGenStarted(boolean bodyGenStarted) {
|
||||||
|
this.bodyGenStarted = bodyGenStarted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,62 @@
|
|||||||
package jadx.core.codegen;
|
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.nodes.ClassNode;
|
||||||
import jadx.core.dex.visitors.AbstractVisitor;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
|
||||||
|
|
||||||
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) {
|
case JSON:
|
||||||
this.args = args;
|
return generateJson(cls);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new JadxRuntimeException("Unknown output format");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private static ICodeInfo generateJavaCode(ClassNode cls, JadxArgs args) {
|
||||||
public boolean visit(ClassNode cls) throws CodegenException {
|
|
||||||
ClassGen clsGen = new ClassGen(cls, args);
|
ClassGen clsGen = new ClassGen(cls, args);
|
||||||
CodeWriter clsCode = clsGen.makeClass();
|
return wrapCodeGen(cls, clsGen::makeClass);
|
||||||
clsCode.finish();
|
|
||||||
cls.setCode(clsCode);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
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.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
public class CodeWriter {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
|
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 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 boolean ADD_LINE_NUMBERS = false;
|
||||||
|
|
||||||
private static final String[] INDENT_CACHE = {
|
private static final String[] INDENT_CACHE = {
|
||||||
"",
|
"",
|
||||||
INDENT,
|
INDENT_STR,
|
||||||
INDENT + INDENT,
|
INDENT_STR + INDENT_STR,
|
||||||
INDENT + INDENT + INDENT,
|
INDENT_STR + INDENT_STR + INDENT_STR,
|
||||||
INDENT + INDENT + INDENT + INDENT,
|
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||||
INDENT + INDENT + INDENT + INDENT + INDENT,
|
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 String indentStr;
|
||||||
private int indent;
|
private int indent;
|
||||||
|
|
||||||
@@ -43,10 +45,12 @@ public class CodeWriter {
|
|||||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||||
|
|
||||||
public CodeWriter() {
|
public CodeWriter() {
|
||||||
|
this.buf = new StringBuilder();
|
||||||
this.indent = 0;
|
this.indent = 0;
|
||||||
this.indentStr = "";
|
this.indentStr = "";
|
||||||
if (ADD_LINE_NUMBERS) {
|
if (ADD_LINE_NUMBERS) {
|
||||||
incIndent(2);
|
incIndent(3);
|
||||||
|
add(indentStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +94,17 @@ public class CodeWriter {
|
|||||||
return this;
|
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) {
|
public CodeWriter add(String str) {
|
||||||
buf.append(str);
|
buf.append(str);
|
||||||
offset += str.length();
|
offset += str.length();
|
||||||
@@ -113,7 +128,7 @@ public class CodeWriter {
|
|||||||
}
|
}
|
||||||
line += code.line;
|
line += code.line;
|
||||||
offset = code.offset;
|
offset = code.offset;
|
||||||
buf.append(code);
|
buf.append(code.buf);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +138,7 @@ public class CodeWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CodeWriter addIndent() {
|
public CodeWriter addIndent() {
|
||||||
add(INDENT);
|
add(INDENT_STR);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +159,7 @@ public class CodeWriter {
|
|||||||
if (curIndent < INDENT_CACHE.length) {
|
if (curIndent < INDENT_CACHE.length) {
|
||||||
this.indentStr = INDENT_CACHE[curIndent];
|
this.indentStr = INDENT_CACHE[curIndent];
|
||||||
} else {
|
} else {
|
||||||
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
|
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
|
||||||
for (int i = 0; i < curIndent; i++) {
|
|
||||||
s.append(INDENT);
|
|
||||||
}
|
|
||||||
this.indentStr = s.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +189,11 @@ public class CodeWriter {
|
|||||||
return indent;
|
return indent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIndent(int indent) {
|
||||||
|
this.indent = indent;
|
||||||
|
updateIndent();
|
||||||
|
}
|
||||||
|
|
||||||
public int getLine() {
|
public int getLine() {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@@ -194,25 +210,26 @@ public class CodeWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object attachDefinition(LineAttrNode obj) {
|
public void attachDefinition(LineAttrNode obj) {
|
||||||
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
attachAnnotation(obj);
|
||||||
|
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object attachAnnotation(Object obj) {
|
public void attachAnnotation(Object obj) {
|
||||||
return attachAnnotation(obj, new CodePosition(line, offset + 1));
|
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) {
|
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||||
if (annotations.isEmpty()) {
|
if (annotations.isEmpty()) {
|
||||||
annotations = new HashMap<CodePosition, Object>();
|
annotations = new HashMap<>();
|
||||||
}
|
}
|
||||||
return annotations.put(pos, obj);
|
return annotations.put(pos, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<CodePosition, Object> getAnnotations() {
|
|
||||||
return annotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void attachSourceLine(int sourceLine) {
|
public void attachSourceLine(int sourceLine) {
|
||||||
if (sourceLine == 0) {
|
if (sourceLine == 0) {
|
||||||
return;
|
return;
|
||||||
@@ -222,104 +239,53 @@ public class CodeWriter {
|
|||||||
|
|
||||||
private void attachSourceLine(int decompiledLine, int sourceLine) {
|
private void attachSourceLine(int decompiledLine, int sourceLine) {
|
||||||
if (lineMap.isEmpty()) {
|
if (lineMap.isEmpty()) {
|
||||||
lineMap = new TreeMap<Integer, Integer>();
|
lineMap = new TreeMap<>();
|
||||||
}
|
}
|
||||||
lineMap.put(decompiledLine, sourceLine);
|
lineMap.put(decompiledLine, sourceLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Integer, Integer> getLineMapping() {
|
public ICodeInfo finish() {
|
||||||
return lineMap;
|
removeFirstEmptyLine();
|
||||||
|
processDefinitionAnnotations();
|
||||||
|
code = buf.toString();
|
||||||
|
buf = null;
|
||||||
|
return new SimpleCodeInfo(code, lineMap, annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void finish() {
|
private void removeFirstEmptyLine() {
|
||||||
buf.trimToSize();
|
int len = NL.length();
|
||||||
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
|
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
||||||
while (it.hasNext()) {
|
buf.delete(0, len);
|
||||||
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 static String removeFirstEmptyLine(String str) {
|
private void processDefinitionAnnotations() {
|
||||||
if (str.startsWith(NL)) {
|
if (!annotations.isEmpty()) {
|
||||||
return str.substring(NL.length());
|
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();
|
return buf.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
public String getCodeStr() {
|
||||||
return buf.length() == 0;
|
if (code == null) {
|
||||||
}
|
finish();
|
||||||
|
}
|
||||||
public boolean notEmpty() {
|
return code;
|
||||||
return buf.length() != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return buf.toString();
|
return code != null ? code : 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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package jadx.core.codegen;
|
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.ArithNode;
|
||||||
import jadx.core.dex.instructions.IfOp;
|
import jadx.core.dex.instructions.IfOp;
|
||||||
import jadx.core.dex.instructions.InsnType;
|
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.Compare;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition;
|
import jadx.core.dex.regions.conditions.IfCondition;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||||
import jadx.core.utils.ErrorsCounter;
|
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
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 {
|
public class ConditionGen extends InsnGen {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
|
|
||||||
|
|
||||||
private static class CondStack {
|
private static class CondStack {
|
||||||
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
|
private final Queue<IfCondition> stack = new LinkedList<>();
|
||||||
|
|
||||||
public Queue<IfCondition> getStack() {
|
public Queue<IfCondition> getStack() {
|
||||||
return stack;
|
return stack;
|
||||||
@@ -126,7 +122,7 @@ public class ConditionGen extends InsnGen {
|
|||||||
wrap(code, firstArg);
|
wrap(code, firstArg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
|
mth.addWarn("Unsupported boolean condition " + op.getSymbol());
|
||||||
}
|
}
|
||||||
|
|
||||||
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
||||||
@@ -159,7 +155,7 @@ public class ConditionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWrapNeeded(IfCondition condition) {
|
private boolean isWrapNeeded(IfCondition condition) {
|
||||||
if (condition.isCompare()) {
|
if (condition.isCompare() || condition.contains(AFlag.DONT_WRAP)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return condition.getMode() != Mode.NOT;
|
return condition.getMode() != Mode.NOT;
|
||||||
@@ -179,6 +175,9 @@ public class ConditionGen extends InsnGen {
|
|||||||
case DIV:
|
case DIV:
|
||||||
case REM:
|
case REM:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (insnType) {
|
switch (insnType) {
|
||||||
@@ -189,10 +188,10 @@ public class ConditionGen extends InsnGen {
|
|||||||
case CONST:
|
case CONST:
|
||||||
case ARRAY_LENGTH:
|
case ARRAY_LENGTH:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
package jadx.core.codegen;
|
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.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
import jadx.core.dex.attributes.AType;
|
||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
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.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.ClassInfo;
|
||||||
import jadx.core.dex.info.FieldInfo;
|
import jadx.core.dex.info.FieldInfo;
|
||||||
import jadx.core.dex.info.MethodInfo;
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.core.dex.instructions.ArithNode;
|
import jadx.core.dex.instructions.ArithNode;
|
||||||
import jadx.core.dex.instructions.ArithOp;
|
import jadx.core.dex.instructions.ArithOp;
|
||||||
|
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||||
import jadx.core.dex.instructions.ConstClassNode;
|
import jadx.core.dex.instructions.ConstClassNode;
|
||||||
import jadx.core.dex.instructions.ConstStringNode;
|
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.FilledNewArrayNode;
|
||||||
import jadx.core.dex.instructions.GotoNode;
|
import jadx.core.dex.instructions.GotoNode;
|
||||||
import jadx.core.dex.instructions.IfNode;
|
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.InvokeNode;
|
||||||
import jadx.core.dex.instructions.InvokeType;
|
import jadx.core.dex.instructions.InvokeType;
|
||||||
import jadx.core.dex.instructions.NewArrayNode;
|
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.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.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
import jadx.core.dex.instructions.args.LiteralArg;
|
import jadx.core.dex.instructions.args.LiteralArg;
|
||||||
import jadx.core.dex.instructions.args.Named;
|
import jadx.core.dex.instructions.args.Named;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
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.ConstructorInsn;
|
||||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||||
import jadx.core.dex.nodes.ClassNode;
|
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.MethodNode;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.utils.RegionUtils;
|
import jadx.core.utils.RegionUtils;
|
||||||
import jadx.core.utils.StringUtils;
|
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import static jadx.core.utils.android.AndroidResourcesUtils.handleAppResField;
|
||||||
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;
|
|
||||||
|
|
||||||
public class InsnGen {
|
public class InsnGen {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
|
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
|
||||||
@@ -61,6 +62,7 @@ public class InsnGen {
|
|||||||
protected final MethodNode mth;
|
protected final MethodNode mth;
|
||||||
protected final RootNode root;
|
protected final RootNode root;
|
||||||
protected final boolean fallback;
|
protected final boolean fallback;
|
||||||
|
protected final boolean attachInsns;
|
||||||
|
|
||||||
protected enum Flags {
|
protected enum Flags {
|
||||||
BODY_ONLY,
|
BODY_ONLY,
|
||||||
@@ -71,8 +73,9 @@ public class InsnGen {
|
|||||||
public InsnGen(MethodGen mgen, boolean fallback) {
|
public InsnGen(MethodGen mgen, boolean fallback) {
|
||||||
this.mgen = mgen;
|
this.mgen = mgen;
|
||||||
this.mth = mgen.getMethodNode();
|
this.mth = mgen.getMethodNode();
|
||||||
this.root = mth.dex().root();
|
this.root = mth.root();
|
||||||
this.fallback = fallback;
|
this.fallback = fallback;
|
||||||
|
this.attachInsns = root.getArgs().isJsonOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFallback() {
|
private boolean isFallback() {
|
||||||
@@ -80,9 +83,9 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||||
int len = code.length();
|
int len = code.bufLength();
|
||||||
addArg(code, arg, true);
|
addArg(code, arg, true);
|
||||||
if (len != code.length()) {
|
if (len != code.bufLength()) {
|
||||||
code.add('.');
|
code.add('.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,22 +100,26 @@ public class InsnGen {
|
|||||||
} else if (arg.isLiteral()) {
|
} else if (arg.isLiteral()) {
|
||||||
code.add(lit((LiteralArg) arg));
|
code.add(lit((LiteralArg) arg));
|
||||||
} else if (arg.isInsnWrap()) {
|
} else if (arg.isInsnWrap()) {
|
||||||
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
addWrappedArg(code, (InsnWrapArg) arg, wrap);
|
||||||
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
|
|
||||||
} else if (arg.isNamed()) {
|
} else if (arg.isNamed()) {
|
||||||
code.add(((Named) arg).getName());
|
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 {
|
} else {
|
||||||
throw new CodegenException("Unknown arg type " + arg);
|
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 {
|
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
|
||||||
RegisterArg arg = insn.getResult();
|
RegisterArg arg = insn.getResult();
|
||||||
if (insn.contains(AFlag.DECLARE_VAR)) {
|
if (insn.contains(AFlag.DECLARE_VAR)) {
|
||||||
@@ -123,63 +130,71 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||||
useType(code, arg.getType());
|
declareVar(code, arg.getSVar().getCodeVar());
|
||||||
code.add(' ');
|
|
||||||
code.add(mgen.getNameGen().assignArg(arg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String lit(LiteralArg arg) {
|
public void declareVar(CodeWriter code, CodeVar codeVar) {
|
||||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
|
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 {
|
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||||
ClassNode pCls = mth.getParentClass();
|
ClassNode pCls = mth.getParentClass();
|
||||||
FieldNode fieldNode = pCls.searchField(field);
|
FieldNode fieldNode = pCls.root().deepResolveField(field);
|
||||||
while (fieldNode == null
|
|
||||||
&& pCls.getParentClass() != pCls
|
|
||||||
&& pCls.getParentClass() != null) {
|
|
||||||
pCls = pCls.getParentClass();
|
|
||||||
fieldNode = pCls.searchField(field);
|
|
||||||
}
|
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||||
if (replace != null) {
|
if (replace != null) {
|
||||||
FieldInfo info = replace.getFieldInfo();
|
switch (replace.getReplaceType()) {
|
||||||
if (replace.isOuterClass()) {
|
case CLASS_INSTANCE:
|
||||||
useClass(code, info.getDeclClass());
|
useClass(code, replace.getClsRef());
|
||||||
code.add(".this");
|
code.add(".this");
|
||||||
|
break;
|
||||||
|
case VAR:
|
||||||
|
addArg(code, replace.getVarRef());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addArgDot(code, arg);
|
addArgDot(code, arg);
|
||||||
fieldNode = mth.dex().resolveField(field);
|
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
code.attachAnnotation(fieldNode);
|
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) {
|
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||||
ClassInfo declClass = field.getDeclClass();
|
ClassInfo declClass = field.getDeclClass();
|
||||||
|
// TODO
|
||||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||||
if (!fieldFromThisClass) {
|
if (!fieldFromThisClass || !clsGen.isBodyGenStarted()) {
|
||||||
// Android specific resources class handler
|
// Android specific resources class handler
|
||||||
ClassInfo parentClass = declClass.getParentClass();
|
if (!handleAppResField(code, clsGen, declClass)) {
|
||||||
if (parentClass != null && parentClass.getShortName().equals("R")) {
|
|
||||||
clsGen.useClass(code, parentClass);
|
|
||||||
code.add('.');
|
|
||||||
code.add(declClass.getAlias().getShortName());
|
|
||||||
} else {
|
|
||||||
clsGen.useClass(code, declClass);
|
clsGen.useClass(code, declClass);
|
||||||
}
|
}
|
||||||
code.add('.');
|
code.add('.');
|
||||||
}
|
}
|
||||||
FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
|
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
|
||||||
if (fieldNode != null) {
|
if (fieldNode != null) {
|
||||||
code.attachAnnotation(fieldNode);
|
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) {
|
protected void staticField(CodeWriter code, FieldInfo field) {
|
||||||
@@ -198,40 +213,54 @@ public class InsnGen {
|
|||||||
mgen.getClassGen().useType(code, type);
|
mgen.getClassGen().useType(code, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
public void makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||||
return makeInsn(insn, code, null);
|
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 {
|
try {
|
||||||
Set<Flags> state = EnumSet.noneOf(Flags.class);
|
|
||||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||||
state.add(flag);
|
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
|
||||||
makeInsnBody(code, insn, state);
|
|
||||||
} else {
|
} else {
|
||||||
if (flag != Flags.INLINE) {
|
if (flag != Flags.INLINE) {
|
||||||
code.startLineWithNum(insn.getSourceLine());
|
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)) {
|
RegisterArg resArg = insn.getResult();
|
||||||
assignVar(code, insn);
|
if (resArg != null) {
|
||||||
code.add(" = ");
|
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) {
|
if (flag != Flags.INLINE) {
|
||||||
code.add(';');
|
code.add(';');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable th) {
|
} catch (Exception e) {
|
||||||
throw new CodegenException(mth, "Error generate insn: " + insn, th);
|
throw new CodegenException(mth, "Error generate insn: " + insn, e);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
||||||
switch (insn.getType()) {
|
switch (insn.getType()) {
|
||||||
case CONST_STR:
|
case CONST_STR:
|
||||||
String str = ((ConstStringNode) insn).getString();
|
String str = ((ConstStringNode) insn).getString();
|
||||||
code.add(StringUtils.unescapeString(str));
|
code.add(mth.root().getStringUtils().unescapeString(str));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CONST_CLASS:
|
case CONST_CLASS:
|
||||||
@@ -269,18 +298,14 @@ public class InsnGen {
|
|||||||
makeArith((ArithNode) insn, code, state);
|
makeArith((ArithNode) insn, code, state);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NEG: {
|
case NEG:
|
||||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
oneArgInsn(code, insn, state, '-');
|
||||||
if (wrap) {
|
break;
|
||||||
code.add('(');
|
|
||||||
}
|
case NOT:
|
||||||
code.add('-');
|
char op = insn.getArg(0).getType() == ArgType.BOOLEAN ? '!' : '~';
|
||||||
addArg(code, insn.getArg(0));
|
oneArgInsn(code, insn, state, op);
|
||||||
if (wrap) {
|
|
||||||
code.add(')');
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case RETURN:
|
case RETURN:
|
||||||
if (insn.getArgsCount() != 0) {
|
if (insn.getArgsCount() != 0) {
|
||||||
@@ -365,6 +390,17 @@ public class InsnGen {
|
|||||||
filledNewArray((FilledNewArrayNode) insn, code);
|
filledNewArray((FilledNewArrayNode) insn, code);
|
||||||
break;
|
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:
|
case AGET:
|
||||||
addArg(code, insn.getArg(0));
|
addArg(code, insn.getArg(0));
|
||||||
code.add('[');
|
code.add('[');
|
||||||
@@ -408,7 +444,7 @@ public class InsnGen {
|
|||||||
if (wrap) {
|
if (wrap) {
|
||||||
code.add('(');
|
code.add('(');
|
||||||
}
|
}
|
||||||
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
|
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext();) {
|
||||||
addArg(code, it.next());
|
addArg(code, it.next());
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
code.add(" + ");
|
code.add(" + ");
|
||||||
@@ -430,7 +466,9 @@ public class InsnGen {
|
|||||||
case MONITOR_EXIT:
|
case MONITOR_EXIT:
|
||||||
if (isFallback()) {
|
if (isFallback()) {
|
||||||
code.add("monitor-exit(");
|
code.add("monitor-exit(");
|
||||||
addArg(code, insn.getArg(0));
|
if (insn.getArgsCount() == 1) {
|
||||||
|
addArg(code, insn.getArg(0));
|
||||||
|
}
|
||||||
code.add(')');
|
code.add(')');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -445,7 +483,7 @@ public class InsnGen {
|
|||||||
|
|
||||||
/* fallback mode instructions */
|
/* fallback mode instructions */
|
||||||
case IF:
|
case IF:
|
||||||
assert isFallback() : "if insn in not fallback mode";
|
fallbackOnlyInsn(insn);
|
||||||
IfNode ifInsn = (IfNode) insn;
|
IfNode ifInsn = (IfNode) insn;
|
||||||
code.add("if (");
|
code.add("if (");
|
||||||
addArg(code, insn.getArg(0));
|
addArg(code, insn.getArg(0));
|
||||||
@@ -456,26 +494,27 @@ public class InsnGen {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case GOTO:
|
case GOTO:
|
||||||
assert isFallback();
|
fallbackOnlyInsn(insn);
|
||||||
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
|
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MOVE_EXCEPTION:
|
case MOVE_EXCEPTION:
|
||||||
assert isFallback();
|
fallbackOnlyInsn(insn);
|
||||||
code.add("move-exception");
|
code.add("move-exception");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SWITCH:
|
case SWITCH:
|
||||||
assert isFallback();
|
fallbackOnlyInsn(insn);
|
||||||
SwitchNode sw = (SwitchNode) insn;
|
SwitchInsn sw = (SwitchInsn) insn;
|
||||||
code.add("switch(");
|
code.add("switch(");
|
||||||
addArg(code, insn.getArg(0));
|
addArg(code, insn.getArg(0));
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
for (int i = 0; i < sw.getCasesCount(); i++) {
|
int[] keys = sw.getKeys();
|
||||||
String key = sw.getKeys()[i].toString();
|
int[] targets = sw.getTargets();
|
||||||
code.startLine("case ").add(key).add(": goto ");
|
for (int i = 0; i < keys.length; i++) {
|
||||||
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
|
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
|
||||||
|
code.add(MethodGen.getLabelName(targets[i])).add(';');
|
||||||
}
|
}
|
||||||
code.startLine("default: goto ");
|
code.startLine("default: goto ");
|
||||||
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
||||||
@@ -483,29 +522,35 @@ public class InsnGen {
|
|||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
break;
|
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:
|
case NEW_INSTANCE:
|
||||||
// only fallback - make new instance in constructor invoke
|
// only fallback - make new instance in constructor invoke
|
||||||
assert isFallback();
|
fallbackOnlyInsn(insn);
|
||||||
code.add("new " + insn.getResult().getType());
|
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;
|
break;
|
||||||
|
|
||||||
default:
|
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 {
|
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
|
||||||
code.add("new ");
|
if (!insn.contains(AFlag.DECLARE_VAR)) {
|
||||||
useType(code, insn.getArrayType());
|
code.add("new ");
|
||||||
|
useType(code, insn.getArrayType());
|
||||||
|
}
|
||||||
code.add('{');
|
code.add('{');
|
||||||
int c = insn.getArgsCount();
|
int c = insn.getArgsCount();
|
||||||
for (int i = 0; i < c; i++) {
|
for (int i = 0; i < c; i++) {
|
||||||
@@ -527,34 +624,12 @@ public class InsnGen {
|
|||||||
code.add('}');
|
code.add('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
|
private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException {
|
||||||
throws CodegenException {
|
ClassNode cls = mth.root().resolveClass(insn.getClassType());
|
||||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||||
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
|
cls.ensureProcessed();
|
||||||
// anonymous class construction
|
inlineAnonymousConstructor(code, cls, insn);
|
||||||
ArgType parent;
|
mth.getParentClass().addInlinedClass(cls);
|
||||||
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);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (insn.isSelf()) {
|
if (insn.isSelf()) {
|
||||||
@@ -567,21 +642,66 @@ public class InsnGen {
|
|||||||
} else {
|
} else {
|
||||||
code.add("new ");
|
code.add("new ");
|
||||||
useClass(code, insn.getClassType());
|
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 {
|
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||||
MethodInfo callMth = insn.getCallMth();
|
MethodInfo callMth = insn.getCallMth();
|
||||||
|
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
||||||
// inline method
|
|
||||||
MethodNode callMthNode = mth.dex().deepResolveMethod(callMth);
|
|
||||||
if (callMthNode != null) {
|
|
||||||
if (inlineMethod(callMthNode, insn, code)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callMth = callMthNode.getMethodInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
int k = 0;
|
int k = 0;
|
||||||
InvokeType type = insn.getInvokeType();
|
InvokeType type = insn.getInvokeType();
|
||||||
@@ -598,13 +718,18 @@ public class InsnGen {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SUPER:
|
case SUPER:
|
||||||
|
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
|
||||||
|
if (superCallCls != null) {
|
||||||
|
useClass(code, superCallCls);
|
||||||
|
code.add('.');
|
||||||
|
}
|
||||||
// use 'super' instead 'this' in 0 arg
|
// use 'super' instead 'this' in 0 arg
|
||||||
code.add("super").add('.');
|
code.add("super").add('.');
|
||||||
k++;
|
k++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STATIC:
|
case STATIC:
|
||||||
ClassInfo insnCls = mth.getParentClass().getAlias();
|
ClassInfo insnCls = mth.getParentClass().getClassInfo();
|
||||||
ClassInfo declClass = callMth.getDeclClass();
|
ClassInfo declClass = callMth.getDeclClass();
|
||||||
if (!insnCls.equals(declClass)) {
|
if (!insnCls.equals(declClass)) {
|
||||||
useClass(code, declClass);
|
useClass(code, declClass);
|
||||||
@@ -614,114 +739,97 @@ public class InsnGen {
|
|||||||
}
|
}
|
||||||
if (callMthNode != null) {
|
if (callMthNode != null) {
|
||||||
code.attachAnnotation(callMthNode);
|
code.attachAnnotation(callMthNode);
|
||||||
|
code.add(callMthNode.getAlias());
|
||||||
|
} else {
|
||||||
|
code.add(callMth.getAlias());
|
||||||
}
|
}
|
||||||
code.add(callMth.getAlias());
|
|
||||||
generateMethodArguments(code, insn, k, callMthNode);
|
generateMethodArguments(code, insn, k, callMthNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
@Nullable
|
||||||
@Nullable MethodNode callMth) throws CodegenException {
|
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;
|
int k = startArgNum;
|
||||||
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
if (mthNode != null && mthNode.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||||
k++;
|
k++;
|
||||||
}
|
}
|
||||||
int argsCount = insn.getArgsCount();
|
int argsCount = insn.getArgsCount();
|
||||||
code.add('(');
|
code.add('(');
|
||||||
|
boolean firstArg = true;
|
||||||
if (k < argsCount) {
|
if (k < argsCount) {
|
||||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
|
||||||
for (int i = k; i < argsCount; i++) {
|
for (int i = k; i < argsCount; i++) {
|
||||||
InsnArg arg = insn.getArg(i);
|
InsnArg arg = insn.getArg(i);
|
||||||
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
|
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, 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;
|
continue;
|
||||||
}
|
}
|
||||||
addArg(code, arg, false);
|
addArg(code, arg, false);
|
||||||
if (i < argsCount - 1) {
|
firstArg = false;
|
||||||
code.add(", ");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code.add(')');
|
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.
|
* Expand varArgs from filled array.
|
||||||
*/
|
*/
|
||||||
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
|
private boolean processVarArg(CodeWriter code, BaseInvokeNode invokeInsn, InsnArg lastArg) throws CodegenException {
|
||||||
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
|
if (!invokeInsn.contains(AFlag.VARARG_CALL)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
||||||
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
|
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) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
InsnNode inl = mia.getInsn();
|
int count = insn.getArgsCount();
|
||||||
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
for (int i = 0; i < count; i++) {
|
||||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
InsnArg elemArg = insn.getArg(i);
|
||||||
} else {
|
addArg(code, elemArg, false);
|
||||||
// remap args
|
if (i < count - 1) {
|
||||||
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
|
code.add(", ");
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -735,11 +843,12 @@ public class InsnGen {
|
|||||||
InsnArg first = insn.getArg(0);
|
InsnArg first = insn.getArg(0);
|
||||||
InsnArg second = insn.getArg(1);
|
InsnArg second = insn.getArg(1);
|
||||||
ConditionGen condGen = new ConditionGen(this);
|
ConditionGen condGen = new ConditionGen(this);
|
||||||
if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
|
if (first.isTrue() && second.isFalse()) {
|
||||||
condGen.add(code, insn.getCondition());
|
condGen.add(code, insn.getCondition());
|
||||||
} else {
|
} else {
|
||||||
condGen.wrap(code, insn.getCondition());
|
condGen.wrap(code, insn.getCondition());
|
||||||
code.add(" ? ");
|
code.add(" ? ");
|
||||||
|
addCastIfNeeded(code, first, second);
|
||||||
addArg(code, first, false);
|
addArg(code, first, false);
|
||||||
code.add(" : ");
|
code.add(" : ");
|
||||||
addArg(code, second, false);
|
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 {
|
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||||
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
||||||
makeArithOneArg(insn, code);
|
makeArithOneArg(insn, code);
|
||||||
@@ -771,19 +907,22 @@ public class InsnGen {
|
|||||||
|
|
||||||
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
|
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
|
||||||
ArithOp op = insn.getOp();
|
ArithOp op = insn.getOp();
|
||||||
|
InsnArg resArg = insn.getArg(0);
|
||||||
InsnArg arg = insn.getArg(1);
|
InsnArg arg = insn.getArg(1);
|
||||||
|
|
||||||
// "++" or "--"
|
// "++" or "--"
|
||||||
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
||||||
LiteralArg lit = (LiteralArg) arg;
|
LiteralArg lit = (LiteralArg) arg;
|
||||||
if (lit.isInteger() && lit.getLiteral() == 1) {
|
if (lit.getLiteral() == 1 && lit.isInteger()) {
|
||||||
assignVar(code, insn);
|
addArg(code, resArg, false);
|
||||||
String opSymbol = op.getSymbol();
|
String opSymbol = op.getSymbol();
|
||||||
code.add(opSymbol).add(opSymbol);
|
code.add(opSymbol).add(opSymbol);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// +=, -= ...
|
|
||||||
assignVar(code, insn);
|
// +=, -=, ...
|
||||||
|
addArg(code, resArg, false);
|
||||||
code.add(' ').add(op.getSymbol()).add("= ");
|
code.add(' ').add(op.getSymbol()).add("= ");
|
||||||
addArg(code, arg, false);
|
addArg(code, arg, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,45 @@
|
|||||||
package jadx.core.codegen;
|
package jadx.core.codegen;
|
||||||
|
|
||||||
import jadx.core.dex.attributes.AFlag;
|
import java.util.Collections;
|
||||||
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.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
public class MethodGen {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||||
@@ -56,8 +70,8 @@ public class MethodGen {
|
|||||||
|
|
||||||
public boolean addDefinition(CodeWriter code) {
|
public boolean addDefinition(CodeWriter code) {
|
||||||
if (mth.getMethodInfo().isClassInit()) {
|
if (mth.getMethodInfo().isClassInit()) {
|
||||||
code.startLine("static");
|
|
||||||
code.attachDefinition(mth);
|
code.attachDefinition(mth);
|
||||||
|
code.startLine("static");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||||
@@ -66,181 +80,329 @@ public class MethodGen {
|
|||||||
code.attachDefinition(mth);
|
code.attachDefinition(mth);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (Consts.DEBUG_USAGE) {
|
||||||
|
ClassGen.addMthUsageInfo(code, mth);
|
||||||
|
}
|
||||||
|
addOverrideAnnotation(code, mth);
|
||||||
annotationGen.addForMethod(code, mth);
|
annotationGen.addForMethod(code, mth);
|
||||||
|
|
||||||
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
|
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
|
||||||
AccessInfo ai = mth.getAccessFlags();
|
AccessInfo ai = mth.getAccessFlags();
|
||||||
// don't add 'abstract' and 'public' to methods in interface
|
// don't add 'abstract' and 'public' to methods in interface
|
||||||
if (clsAccFlags.isInterface()) {
|
if (clsAccFlags.isInterface()) {
|
||||||
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
|
ai = ai.remove(AccessFlags.ABSTRACT);
|
||||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
ai = ai.remove(AccessFlags.PUBLIC);
|
||||||
}
|
}
|
||||||
// don't add 'public' for annotations
|
// don't add 'public' for annotations
|
||||||
if (clsAccFlags.isAnnotation()) {
|
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.startLineWithNum(mth.getSourceLine());
|
||||||
code.add(ai.makeString());
|
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(' ');
|
code.add(' ');
|
||||||
}
|
}
|
||||||
if (mth.getAccessFlags().isConstructor()) {
|
if (ai.isConstructor()) {
|
||||||
|
code.attachDefinition(mth);
|
||||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||||
} else {
|
} else {
|
||||||
classGen.useType(code, mth.getReturnType());
|
classGen.useType(code, mth.getReturnType());
|
||||||
code.add(' ');
|
code.add(' ');
|
||||||
|
code.attachDefinition(mth);
|
||||||
code.add(mth.getAlias());
|
code.add(mth.getAlias());
|
||||||
}
|
}
|
||||||
code.add('(');
|
code.add('(');
|
||||||
|
|
||||||
List<RegisterArg> args = mth.getArguments(false);
|
List<RegisterArg> args = mth.getArgRegs();
|
||||||
if (mth.getMethodInfo().isConstructor()
|
if (mth.getMethodInfo().isConstructor()
|
||||||
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
|
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
|
||||||
if (args.size() == 2) {
|
if (args.size() == 2) {
|
||||||
args.clear();
|
args = Collections.emptyList();
|
||||||
} else if (args.size() > 2) {
|
} else if (args.size() > 2) {
|
||||||
args = args.subList(2, args.size());
|
args = args.subList(2, args.size());
|
||||||
} else {
|
} else {
|
||||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
|
mth.addComment("JADX WARN: Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
|
||||||
"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);
|
addMethodArguments(code, args);
|
||||||
code.add(')');
|
code.add(')');
|
||||||
|
|
||||||
annotationGen.addThrows(mth, code);
|
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;
|
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);
|
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
|
Iterator<RegisterArg> it = args.iterator();
|
||||||
RegisterArg arg = it.next();
|
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
|
// add argument annotation
|
||||||
if (paramsAnnotation != null) {
|
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()) {
|
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||||
// change last array argument to varargs
|
// change last array argument to varargs
|
||||||
ArgType type = arg.getType();
|
if (argType.isArray()) {
|
||||||
if (type.isArray()) {
|
ArgType elType = argType.getArrayElement();
|
||||||
ArgType elType = type.getArrayElement();
|
classGen.useType(code, elType);
|
||||||
classGen.useType(argsCode, elType);
|
code.add("...");
|
||||||
argsCode.add("...");
|
|
||||||
} else {
|
} else {
|
||||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
|
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var);
|
||||||
classGen.useType(argsCode, arg.getType());
|
classGen.useType(code, argType);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
classGen.useType(argsCode, arg.getType());
|
classGen.useType(code, argType);
|
||||||
}
|
}
|
||||||
argsCode.add(' ');
|
code.add(' ');
|
||||||
argsCode.add(nameGen.assignArg(arg));
|
code.add(nameGen.assignArg(var));
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
argsCode.add(", ");
|
code.add(", ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addInstructions(CodeWriter code) throws CodegenException {
|
public void addInstructions(CodeWriter code) throws CodegenException {
|
||||||
if (mth.contains(AType.JADX_ERROR)
|
if (mth.root().getArgs().isFallbackMode()) {
|
||||||
|| mth.contains(AFlag.INCONSISTENT_CODE)
|
addFallbackMethodCode(code, FALLBACK_MODE);
|
||||||
|| mth.getRegion() == null) {
|
} else if (classGen.isFallbackMode()) {
|
||||||
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
|
dumpInstructions(code);
|
||||||
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("\");");
|
|
||||||
} else {
|
} else {
|
||||||
RegionGen regionGen = new RegionGen(this);
|
addRegionInsns(code);
|
||||||
regionGen.makeRegion(code, mth.getRegion());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFallbackMethodCode(CodeWriter code) {
|
public void addRegionInsns(CodeWriter code) throws CodegenException {
|
||||||
if (mth.getInstructions() == null) {
|
try {
|
||||||
JadxErrorAttr errorAttr = mth.get(AType.JADX_ERROR);
|
RegionGen regionGen = new RegionGen(this);
|
||||||
if (errorAttr == null
|
regionGen.makeRegion(code, mth.getRegion());
|
||||||
|| errorAttr.getCause() == null
|
} catch (StackOverflowError | BootstrapMethodError e) {
|
||||||
|| !errorAttr.getCause().getClass().equals(DecodeException.class)) {
|
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
|
||||||
// load original instructions
|
classGen.insertDecompilationProblems(code, mth);
|
||||||
try {
|
dumpInstructions(code);
|
||||||
mth.load();
|
} catch (Exception e) {
|
||||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||||
} catch (DecodeException e) {
|
throw e;
|
||||||
LOG.error("Error reload instructions in fallback mode:", e);
|
|
||||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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();
|
InsnNode[] insnArr = mth.getInstructions();
|
||||||
if (insnArr == null) {
|
if (insnArr == null) {
|
||||||
code.startLine("// Can't load method instructions.");
|
code.startLine("// Can't load method instructions.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (insnArr.length > 100) {
|
||||||
|
code.startLine("// Method dump skipped, instructions count: " + insnArr.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
code.incIndent();
|
||||||
if (mth.getThisArg() != null) {
|
if (mth.getThisArg() != null) {
|
||||||
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
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);
|
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||||
|
boolean attachInsns = mth.root().getArgs().isJsonOutput();
|
||||||
|
InsnNode prevInsn = null;
|
||||||
for (InsnNode insn : insnArr) {
|
for (InsnNode insn : insnArr) {
|
||||||
if (insn == null || insn.getType() == InsnType.NOP) {
|
if (insn == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
|
if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) {
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine(getLabelName(insn.getOffset()) + ":");
|
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
}
|
}
|
||||||
|
if (insn.getType() == InsnType.NOP) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (insnGen.makeInsn(insn, code)) {
|
boolean escapeComment = isCommentEscapeNeeded(insn, option);
|
||||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
if (escapeComment) {
|
||||||
if (catchAttr != null) {
|
code.decIndent();
|
||||||
code.add("\t " + catchAttr);
|
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());
|
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||||
|
code.setIndent(startIndent);
|
||||||
code.startLine("// error: " + insn);
|
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
|
* Return fallback variant of method codegen
|
||||||
*/
|
*/
|
||||||
public static MethodGen getFallbackMethodGen(MethodNode mth) {
|
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);
|
return new MethodGen(clsGen, mth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getLabelName(int offset) {
|
public static String getLabelName(int offset) {
|
||||||
return "L_" + InsnUtils.formatOffset(offset);
|
return "L_" + InsnUtils.formatOffset(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package jadx.core.codegen;
|
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.Consts;
|
||||||
import jadx.core.deobf.NameMapper;
|
import jadx.core.deobf.NameMapper;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
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.info.MethodInfo;
|
||||||
import jadx.core.dex.instructions.InvokeNode;
|
import jadx.core.dex.instructions.InvokeNode;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
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.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||||
import jadx.core.dex.instructions.args.NamedArg;
|
import jadx.core.dex.instructions.args.NamedArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
import jadx.core.dex.instructions.args.RegisterArg;
|
||||||
import jadx.core.dex.instructions.args.SSAVar;
|
import jadx.core.dex.instructions.args.SSAVar;
|
||||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
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.InsnNode;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
import jadx.core.utils.StringUtils;
|
import jadx.core.utils.StringUtils;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class NameGen {
|
public class NameGen {
|
||||||
|
|
||||||
private static final Map<String, String> OBJ_ALIAS;
|
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 MethodNode mth;
|
||||||
private final boolean fallback;
|
private final boolean fallback;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
OBJ_ALIAS = new HashMap<String, String>();
|
OBJ_ALIAS = Utils.newConstStringMap(
|
||||||
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
|
Consts.CLASS_STRING, "str",
|
||||||
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
|
Consts.CLASS_CLASS, "cls",
|
||||||
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
|
Consts.CLASS_THROWABLE, "th",
|
||||||
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
|
Consts.CLASS_OBJECT, "obj",
|
||||||
OBJ_ALIAS.put("java.util.Iterator", "it");
|
"java.util.Iterator", "it",
|
||||||
OBJ_ALIAS.put("java.lang.Boolean", "bool");
|
"java.lang.Boolean", "bool",
|
||||||
OBJ_ALIAS.put("java.lang.Short", "sh");
|
"java.lang.Short", "sh",
|
||||||
OBJ_ALIAS.put("java.lang.Integer", "num");
|
"java.lang.Integer", "num",
|
||||||
OBJ_ALIAS.put("java.lang.Character", "ch");
|
"java.lang.Character", "ch",
|
||||||
OBJ_ALIAS.put("java.lang.Byte", "b");
|
"java.lang.Byte", "b",
|
||||||
OBJ_ALIAS.put("java.lang.Float", "f");
|
"java.lang.Float", "f",
|
||||||
OBJ_ALIAS.put("java.lang.Long", "l");
|
"java.lang.Long", "l",
|
||||||
OBJ_ALIAS.put("java.lang.Double", "d");
|
"java.lang.Double", "d",
|
||||||
|
"java.lang.StringBuilder", "sb",
|
||||||
|
"java.lang.Exception", "exc");
|
||||||
}
|
}
|
||||||
|
|
||||||
public NameGen(MethodNode mth, boolean fallback) {
|
public NameGen(MethodNode mth, boolean fallback) {
|
||||||
this.mth = mth;
|
this.mth = mth;
|
||||||
this.fallback = fallback;
|
this.fallback = fallback;
|
||||||
|
addNamesUsedInClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String assignArg(RegisterArg arg) {
|
private void addNamesUsedInClass() {
|
||||||
String name = makeArgName(arg);
|
ClassNode parentClass = mth.getParentClass();
|
||||||
if (fallback) {
|
for (FieldNode field : parentClass.getFields()) {
|
||||||
return name;
|
varNames.add(field.getAlias());
|
||||||
}
|
}
|
||||||
name = getUniqueVarName(name);
|
for (ClassNode innerClass : parentClass.getInnerClasses()) {
|
||||||
arg.setName(name);
|
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;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,54 +119,60 @@ public class NameGen {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeArgName(RegisterArg arg) {
|
private String makeArgName(CodeVar var) {
|
||||||
if (fallback) {
|
String name = var.getName();
|
||||||
return getFallbackName(arg);
|
if (name == null) {
|
||||||
|
name = guessName(var);
|
||||||
}
|
}
|
||||||
String name = arg.getName();
|
if (!NameMapper.isValidAndPrintable(name)) {
|
||||||
String varName;
|
name = getFallbackName(var);
|
||||||
if (name != null) {
|
|
||||||
if ("this".equals(name)) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
varName = name;
|
|
||||||
} else {
|
|
||||||
varName = guessName(arg);
|
|
||||||
}
|
}
|
||||||
if (NameMapper.isReserved(varName)) {
|
if (Consts.DEBUG) {
|
||||||
return varName + "R";
|
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) {
|
private String getFallbackName(RegisterArg arg) {
|
||||||
return "r" + arg.getRegNum();
|
return "r" + arg.getRegNum();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String guessName(RegisterArg arg) {
|
private String guessName(CodeVar var) {
|
||||||
SSAVar sVar = arg.getSVar();
|
List<SSAVar> ssaVars = var.getSsaVars();
|
||||||
if (sVar != null && sVar.getName() == null) {
|
if (ssaVars != null && !ssaVars.isEmpty()) {
|
||||||
RegisterArg assignArg = sVar.getAssign();
|
// TODO: use all vars for better name generation
|
||||||
InsnNode assignInsn = assignArg.getParentInsn();
|
SSAVar ssaVar = ssaVars.get(0);
|
||||||
if (assignInsn != null) {
|
if (ssaVar != null && ssaVar.getName() == null) {
|
||||||
String name = makeNameFromInsn(assignInsn);
|
RegisterArg assignArg = ssaVar.getAssign();
|
||||||
if (name != null && !NameMapper.isReserved(name)) {
|
InsnNode assignInsn = assignArg.getParentInsn();
|
||||||
assignArg.setName(name);
|
if (assignInsn != null) {
|
||||||
return name;
|
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) {
|
private String makeNameForType(ArgType type) {
|
||||||
if (type.isPrimitive()) {
|
if (type.isPrimitive()) {
|
||||||
return makeNameForPrimitive(type);
|
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) {
|
private static String makeNameForPrimitive(ArgType type) {
|
||||||
@@ -153,17 +180,23 @@ public class NameGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String makeNameForObject(ArgType type) {
|
private String makeNameForObject(ArgType type) {
|
||||||
|
if (type.isGenericType()) {
|
||||||
|
return StringUtils.escape(type.getObject().toLowerCase());
|
||||||
|
}
|
||||||
if (type.isObject()) {
|
if (type.isObject()) {
|
||||||
String alias = getAliasForObject(type.getObject());
|
String alias = getAliasForObject(type.getObject());
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
return alias;
|
return alias;
|
||||||
}
|
}
|
||||||
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
|
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
|
||||||
String shortName = extClsInfo.getShortName();
|
String shortName = extClsInfo.getShortName();
|
||||||
String vName = fromName(shortName);
|
String vName = fromName(shortName);
|
||||||
if (vName != null) {
|
if (vName != null) {
|
||||||
return vName;
|
return vName;
|
||||||
}
|
}
|
||||||
|
if (shortName != null) {
|
||||||
|
return StringUtils.escape(shortName.toLowerCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return StringUtils.escape(type.toString());
|
return StringUtils.escape(type.toString());
|
||||||
}
|
}
|
||||||
@@ -228,16 +261,19 @@ public class NameGen {
|
|||||||
if (name.startsWith("get") || name.startsWith("set")) {
|
if (name.startsWith("get") || name.startsWith("set")) {
|
||||||
return fromName(name.substring(3));
|
return fromName(name.substring(3));
|
||||||
}
|
}
|
||||||
ArgType declType = callMth.getDeclClass().getAlias().getType();
|
|
||||||
if ("iterator".equals(name)) {
|
if ("iterator".equals(name)) {
|
||||||
return "it";
|
return "it";
|
||||||
}
|
}
|
||||||
|
ArgType declType = callMth.getDeclClass().getType();
|
||||||
if ("toString".equals(name)) {
|
if ("toString".equals(name)) {
|
||||||
return makeNameForType(declType);
|
return makeNameForType(declType);
|
||||||
}
|
}
|
||||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
||||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||||
}
|
}
|
||||||
|
if (name.startsWith("to")) {
|
||||||
|
return fromName(name.substring(2));
|
||||||
|
}
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
package jadx.core.codegen;
|
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.AFlag;
|
||||||
import jadx.core.dex.attributes.AType;
|
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.DeclareVariablesAttr;
|
||||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
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.InsnArg;
|
||||||
import jadx.core.dex.instructions.args.NamedArg;
|
import jadx.core.dex.instructions.args.NamedArg;
|
||||||
import jadx.core.dex.instructions.args.RegisterArg;
|
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.IContainer;
|
||||||
import jadx.core.dex.nodes.IRegion;
|
import jadx.core.dex.nodes.IRegion;
|
||||||
import jadx.core.dex.nodes.InsnNode;
|
import jadx.core.dex.nodes.InsnNode;
|
||||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
|
||||||
import jadx.core.dex.regions.Region;
|
import jadx.core.dex.regions.Region;
|
||||||
import jadx.core.dex.regions.SwitchRegion;
|
import jadx.core.dex.regions.SwitchRegion;
|
||||||
|
import jadx.core.dex.regions.SwitchRegion.CaseInfo;
|
||||||
import jadx.core.dex.regions.SynchronizedRegion;
|
import jadx.core.dex.regions.SynchronizedRegion;
|
||||||
import jadx.core.dex.regions.TryCatchRegion;
|
import jadx.core.dex.regions.TryCatchRegion;
|
||||||
import jadx.core.dex.regions.conditions.IfCondition;
|
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.LoopRegion;
|
||||||
import jadx.core.dex.regions.loops.LoopType;
|
import jadx.core.dex.regions.loops.LoopType;
|
||||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
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.RegionUtils;
|
||||||
import jadx.core.utils.exceptions.CodegenException;
|
import jadx.core.utils.exceptions.CodegenException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
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 {
|
public class RegionGen extends InsnGen {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
|
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) {
|
private void declareVars(CodeWriter code, IContainer cont) {
|
||||||
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
||||||
if (declVars != null) {
|
if (declVars != null) {
|
||||||
for (RegisterArg v : declVars.getVars()) {
|
for (CodeVar v : declVars.getVars()) {
|
||||||
code.startLine();
|
code.startLine();
|
||||||
declareVar(code, v);
|
declareVar(code, v);
|
||||||
code.add(';');
|
code.add(';');
|
||||||
@@ -95,8 +101,12 @@ public class RegionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
||||||
|
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (InsnNode insn : block.getInstructions()) {
|
for (InsnNode insn : block.getInstructions()) {
|
||||||
if (!insn.contains(AFlag.SKIP)) {
|
if (!insn.contains(AFlag.DONT_GENERATE)) {
|
||||||
makeInsn(insn, code);
|
makeInsn(insn, code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,21 +122,44 @@ public class RegionGen extends InsnGen {
|
|||||||
} else {
|
} else {
|
||||||
code.attachSourceLine(region.getSourceLine());
|
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 (");
|
code.add("if (");
|
||||||
new ConditionGen(this).add(code, region.getCondition());
|
new ConditionGen(this).add(code, region.getCondition());
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
makeRegionIndent(code, region.getThenRegion());
|
makeRegionIndent(code, region.getThenRegion());
|
||||||
code.startLine('}');
|
if (comment) {
|
||||||
|
code.startLine("// }");
|
||||||
|
} else {
|
||||||
|
code.startLine('}');
|
||||||
|
}
|
||||||
|
|
||||||
IContainer els = region.getElseRegion();
|
IContainer els = region.getElseRegion();
|
||||||
if (els != null && RegionUtils.notEmpty(els)) {
|
if (RegionUtils.notEmpty(els)) {
|
||||||
code.add(" else ");
|
code.add(" else ");
|
||||||
if (connectElseIf(code, els)) {
|
if (connectElseIf(code, els)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
code.add('{');
|
code.add('{');
|
||||||
makeRegionIndent(code, els);
|
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
|
* Connect if-else-if block
|
||||||
*/
|
*/
|
||||||
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
|
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
|
||||||
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
|
if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) {
|
||||||
return false;
|
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
||||||
}
|
if (subBlocks.size() == 1) {
|
||||||
if (!(els instanceof Region)) {
|
IContainer elseBlock = subBlocks.get(0);
|
||||||
return false;
|
if (elseBlock instanceof IfRegion) {
|
||||||
}
|
declareVars(code, elseBlock);
|
||||||
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
makeIf((IfRegion) elseBlock, code, false);
|
||||||
if (subBlocks.size() == 1
|
return true;
|
||||||
&& subBlocks.get(0) instanceof IfRegion) {
|
}
|
||||||
makeIf((IfRegion) subBlocks.get(0), code, false);
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
|
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);
|
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||||
if (labelAttr != null) {
|
if (labelAttr != null) {
|
||||||
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
||||||
@@ -207,11 +227,13 @@ public class RegionGen extends InsnGen {
|
|||||||
if (region.isConditionAtEnd()) {
|
if (region.isConditionAtEnd()) {
|
||||||
code.startLine("do {");
|
code.startLine("do {");
|
||||||
makeRegionIndent(code, region.getBody());
|
makeRegionIndent(code, region.getBody());
|
||||||
code.startLine("} while (");
|
code.startLineWithNum(region.getConditionSourceLine());
|
||||||
|
code.add("} while (");
|
||||||
conditionGen.add(code, condition);
|
conditionGen.add(code, condition);
|
||||||
code.add(");");
|
code.add(");");
|
||||||
} else {
|
} else {
|
||||||
code.startLine("while (");
|
code.startLineWithNum(region.getConditionSourceLine());
|
||||||
|
code.add("while (");
|
||||||
conditionGen.add(code, condition);
|
conditionGen.add(code, condition);
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
makeRegionIndent(code, region.getBody());
|
makeRegionIndent(code, region.getBody());
|
||||||
@@ -229,49 +251,56 @@ public class RegionGen extends InsnGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
|
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);
|
InsnArg arg = insn.getArg(0);
|
||||||
code.startLine("switch (");
|
code.startLine("switch (");
|
||||||
addArg(code, arg, false);
|
addArg(code, arg, false);
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
code.incIndent();
|
code.incIndent();
|
||||||
|
|
||||||
int size = sw.getKeys().size();
|
for (CaseInfo caseInfo : sw.getCases()) {
|
||||||
for (int i = 0; i < size; i++) {
|
List<Object> keys = caseInfo.getKeys();
|
||||||
List<Object> keys = sw.getKeys().get(i);
|
IContainer c = caseInfo.getContainer();
|
||||||
IContainer c = sw.getCases().get(i);
|
|
||||||
for (Object k : keys) {
|
for (Object k : keys) {
|
||||||
code.startLine("case ");
|
if (k == SwitchRegion.DEFAULT_CASE_KEY) {
|
||||||
if (k instanceof FieldNode) {
|
code.startLine("default:");
|
||||||
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()));
|
|
||||||
} else {
|
} 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);
|
makeRegionIndent(code, c);
|
||||||
}
|
}
|
||||||
if (sw.getDefaultCase() != null) {
|
|
||||||
code.startLine("default:");
|
|
||||||
makeRegionIndent(code, sw.getDefaultCase());
|
|
||||||
}
|
|
||||||
code.decIndent();
|
code.decIndent();
|
||||||
code.startLine('}');
|
code.startLine('}');
|
||||||
return code;
|
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 {
|
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
|
||||||
code.startLine("try {");
|
code.startLine("try {");
|
||||||
makeRegionIndent(code, region.getTryRegion());
|
makeRegionIndent(code, region.getTryRegion());
|
||||||
@@ -305,17 +334,29 @@ public class RegionGen extends InsnGen {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
code.startLine("} catch (");
|
code.startLine("} catch (");
|
||||||
InsnArg arg = handler.getArg();
|
if (handler.isCatchAll()) {
|
||||||
if (arg instanceof RegisterArg) {
|
useClass(code, ArgType.THROWABLE);
|
||||||
declareVar(code, (RegisterArg) arg);
|
} else {
|
||||||
} else if (arg instanceof NamedArg) {
|
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
|
||||||
if (handler.isCatchAll()) {
|
if (it.hasNext()) {
|
||||||
code.add("Throwable");
|
useClass(code, it.next());
|
||||||
} else {
|
|
||||||
useClass(code, handler.getCatchType());
|
|
||||||
}
|
}
|
||||||
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));
|
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||||
|
} else {
|
||||||
|
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
code.add(") {");
|
code.add(") {");
|
||||||
makeRegionIndent(code, region);
|
makeRegionIndent(code, region);
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
package jadx.core.codegen;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
public class TypeGen {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
|
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
|
||||||
|
|
||||||
@@ -26,18 +29,39 @@ public class TypeGen {
|
|||||||
return stype.getShortName();
|
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
|
* Convert literal value to string according to value type
|
||||||
*
|
*
|
||||||
* @throws JadxRuntimeException for incorrect type or literal value
|
* @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()) {
|
if (type == null || !type.isTypeKnown()) {
|
||||||
String n = Long.toString(lit);
|
String n = Long.toString(lit);
|
||||||
if (Math.abs(lit) > 100) {
|
if (fallback && Math.abs(lit) > 100) {
|
||||||
n += "; // 0x" + Long.toHexString(lit)
|
StringBuilder sb = new StringBuilder();
|
||||||
+ " float:" + Float.intBitsToFloat((int) lit)
|
sb.append(n).append("(0x").append(Long.toHexString(lit));
|
||||||
+ " double:" + Double.longBitsToDouble(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;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -46,15 +70,15 @@ public class TypeGen {
|
|||||||
case BOOLEAN:
|
case BOOLEAN:
|
||||||
return lit == 0 ? "false" : "true";
|
return lit == 0 ? "false" : "true";
|
||||||
case CHAR:
|
case CHAR:
|
||||||
return StringUtils.unescapeChar((char) lit);
|
return stringUtils.unescapeChar((char) lit, cast);
|
||||||
case BYTE:
|
case BYTE:
|
||||||
return formatByte((byte) lit);
|
return formatByte(lit, cast);
|
||||||
case SHORT:
|
case SHORT:
|
||||||
return formatShort((short) lit);
|
return formatShort(lit, cast);
|
||||||
case INT:
|
case INT:
|
||||||
return formatInteger((int) lit);
|
return formatInteger(lit, cast);
|
||||||
case LONG:
|
case LONG:
|
||||||
return formatLong(lit);
|
return formatLong(lit, cast);
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
return formatFloat(Float.intBitsToFloat((int) lit));
|
return formatFloat(Float.intBitsToFloat((int) lit));
|
||||||
case DOUBLE:
|
case DOUBLE:
|
||||||
@@ -73,37 +97,40 @@ public class TypeGen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatShort(short s) {
|
public static String formatShort(long l, boolean cast) {
|
||||||
if (s == Short.MAX_VALUE) {
|
if (l == Short.MAX_VALUE) {
|
||||||
return "Short.MAX_VALUE";
|
return "Short.MAX_VALUE";
|
||||||
}
|
}
|
||||||
if (s == Short.MIN_VALUE) {
|
if (l == Short.MIN_VALUE) {
|
||||||
return "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) {
|
public static String formatByte(long l, boolean cast) {
|
||||||
if (b == Byte.MAX_VALUE) {
|
if (l == Byte.MAX_VALUE) {
|
||||||
return "Byte.MAX_VALUE";
|
return "Byte.MAX_VALUE";
|
||||||
}
|
}
|
||||||
if (b == Byte.MIN_VALUE) {
|
if (l == Byte.MIN_VALUE) {
|
||||||
return "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) {
|
public static String formatInteger(long l, boolean cast) {
|
||||||
if (i == Integer.MAX_VALUE) {
|
if (l == Integer.MAX_VALUE) {
|
||||||
return "Integer.MAX_VALUE";
|
return "Integer.MAX_VALUE";
|
||||||
}
|
}
|
||||||
if (i == Integer.MIN_VALUE) {
|
if (l == Integer.MIN_VALUE) {
|
||||||
return "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) {
|
if (l == Long.MAX_VALUE) {
|
||||||
return "Long.MAX_VALUE";
|
return "Long.MAX_VALUE";
|
||||||
}
|
}
|
||||||
@@ -111,8 +138,8 @@ public class TypeGen {
|
|||||||
return "Long.MIN_VALUE";
|
return "Long.MIN_VALUE";
|
||||||
}
|
}
|
||||||
String str = Long.toString(l);
|
String str = Long.toString(l);
|
||||||
if (Math.abs(l) >= Integer.MAX_VALUE) {
|
if (cast || Math.abs(l) >= Integer.MAX_VALUE) {
|
||||||
str += "L";
|
return str + 'L';
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
@@ -136,7 +163,7 @@ public class TypeGen {
|
|||||||
if (d == Double.MIN_NORMAL) {
|
if (d == Double.MIN_NORMAL) {
|
||||||
return "Double.MIN_NORMAL";
|
return "Double.MIN_NORMAL";
|
||||||
}
|
}
|
||||||
return Double.toString(d) + "d";
|
return Double.toString(d) + 'd';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatFloat(float f) {
|
public static String formatFloat(float f) {
|
||||||
@@ -158,7 +185,6 @@ public class TypeGen {
|
|||||||
if (f == Float.MIN_NORMAL) {
|
if (f == Float.MIN_NORMAL) {
|
||||||
return "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;
|
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.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
class DeobfPresets {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
|
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 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> clsPresetMap = new HashMap<>();
|
||||||
private final Map<String, String> fldPresetMap = new HashMap<String, String>();
|
private final Map<String, String> fldPresetMap = new HashMap<>();
|
||||||
private final Map<String, String> mthPresetMap = new HashMap<String, String>();
|
private final Map<String, String> mthPresetMap = new HashMap<>();
|
||||||
|
|
||||||
public DeobfPresets(Deobfuscator deobfuscator, File deobfMapFile) {
|
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) {
|
||||||
this.deobfuscator = deobfuscator;
|
this.deobfuscator = deobfuscator;
|
||||||
this.deobfMapFile = deobfMapFile;
|
this.deobfMapFile = deobfMapFile;
|
||||||
}
|
}
|
||||||
@@ -37,12 +40,12 @@ class DeobfPresets {
|
|||||||
* Loads deobfuscator presets
|
* Loads deobfuscator presets
|
||||||
*/
|
*/
|
||||||
public void load() {
|
public void load() {
|
||||||
if (!deobfMapFile.exists()) {
|
if (!Files.exists(deobfMapFile)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOG.info("Loading obfuscation map from: {}", deobfMapFile.getAbsoluteFile());
|
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
|
||||||
try {
|
try {
|
||||||
List<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET);
|
List<String> lines = Files.readAllLines(deobfMapFile, MAP_FILE_CHARSET);
|
||||||
for (String l : lines) {
|
for (String l : lines) {
|
||||||
l = l.trim();
|
l = l.trim();
|
||||||
if (l.isEmpty() || l.startsWith("#")) {
|
if (l.isEmpty() || l.startsWith("#")) {
|
||||||
@@ -65,7 +68,7 @@ class DeobfPresets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} 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) {
|
public void save(boolean forceSave) {
|
||||||
try {
|
try {
|
||||||
if (deobfMapFile.exists()) {
|
if (Files.exists(deobfMapFile)) {
|
||||||
if (forceSave) {
|
if (forceSave) {
|
||||||
dumpMapping();
|
dumpMapping();
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||||
deobfMapFile.getAbsolutePath());
|
deobfMapFile.toAbsolutePath());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dumpMapping();
|
dumpMapping();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} 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
|
* Saves DefaultDeobfuscator presets
|
||||||
*/
|
*/
|
||||||
private void dumpMapping() throws IOException {
|
private void dumpMapping() throws IOException {
|
||||||
List<String> list = new ArrayList<String>();
|
List<String> list = new ArrayList<>();
|
||||||
// packages
|
// packages
|
||||||
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
|
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
|
||||||
for (PackageNode pp : p.getInnerPackages()) {
|
for (PackageNode pp : p.getInnerPackages()) {
|
||||||
@@ -112,18 +115,20 @@ class DeobfPresets {
|
|||||||
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
|
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
|
||||||
if (deobfClsInfo.getAlias() != null) {
|
if (deobfClsInfo.getAlias() != null) {
|
||||||
list.add(String.format("c %s = %s",
|
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()) {
|
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()) {
|
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);
|
Collections.sort(list);
|
||||||
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
|
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
|
||||||
list.clear();
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
|
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
|
||||||
@@ -136,15 +141,15 @@ class DeobfPresets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getForCls(ClassInfo cls) {
|
public String getForCls(ClassInfo cls) {
|
||||||
return clsPresetMap.get(cls.getFullName());
|
return clsPresetMap.get(cls.makeRawFullName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getForFld(FieldInfo fld) {
|
public String getForFld(FieldInfo fld) {
|
||||||
return fldPresetMap.get(fld.getFullId());
|
return fldPresetMap.get(fld.getRawFullId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getForMth(MethodInfo mth) {
|
public String getForMth(MethodInfo mth) {
|
||||||
return mthPresetMap.get(mth.getFullId());
|
return mthPresetMap.get(mth.getRawFullId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
|||||||
@@ -1,28 +1,35 @@
|
|||||||
package jadx.core.deobf;
|
package jadx.core.deobf;
|
||||||
|
|
||||||
import jadx.api.IJadxArgs;
|
import java.nio.file.Path;
|
||||||
import jadx.core.dex.attributes.AType;
|
import java.util.ArrayList;
|
||||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
import java.util.Collections;
|
||||||
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.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 {
|
public class Deobfuscator {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
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 CLASS_NAME_SEPARATOR = ".";
|
||||||
public static final String INNER_CLASS_SEPARATOR = "$";
|
public static final String INNER_CLASS_SEPARATOR = "$";
|
||||||
|
|
||||||
private final IJadxArgs args;
|
private final JadxArgs args;
|
||||||
@NotNull
|
private final RootNode root;
|
||||||
private final List<DexNode> dexNodes;
|
|
||||||
private final DeobfPresets deobfPresets;
|
private final DeobfPresets deobfPresets;
|
||||||
|
|
||||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
|
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
|
||||||
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
|
private final Map<FieldInfo, String> fldMap = new HashMap<>();
|
||||||
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
|
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 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 maxLength;
|
||||||
private final int minLength;
|
private final int minLength;
|
||||||
|
private final boolean useSourceNameAsAlias;
|
||||||
|
private final boolean parseKotlinMetadata;
|
||||||
|
|
||||||
private int pkgIndex = 0;
|
private int pkgIndex = 0;
|
||||||
private int clsIndex = 0;
|
private int clsIndex = 0;
|
||||||
private int fldIndex = 0;
|
private int fldIndex = 0;
|
||||||
private int mthIndex = 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.args = args;
|
||||||
this.dexNodes = dexNodes;
|
this.root = root;
|
||||||
|
|
||||||
this.minLength = args.getDeobfuscationMinLength();
|
this.minLength = args.getDeobfuscationMinLength();
|
||||||
this.maxLength = args.getDeobfuscationMaxLength();
|
this.maxLength = args.getDeobfuscationMaxLength();
|
||||||
|
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
|
||||||
|
this.parseKotlinMetadata = args.isParseKotlinMetadata();
|
||||||
|
|
||||||
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
||||||
}
|
}
|
||||||
@@ -66,8 +81,20 @@ public class Deobfuscator {
|
|||||||
initIndexes();
|
initIndexes();
|
||||||
}
|
}
|
||||||
process();
|
process();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void savePresets() {
|
||||||
deobfPresets.save(args.isDeobfuscationForceSave());
|
deobfPresets.save(args.isDeobfuscationForceSave());
|
||||||
clear();
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
deobfPresets.clear();
|
||||||
|
clsMap.clear();
|
||||||
|
fldMap.clear();
|
||||||
|
mthMap.clear();
|
||||||
|
|
||||||
|
ovrd.clear();
|
||||||
|
ovrdMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initIndexes() {
|
private void initIndexes() {
|
||||||
@@ -78,10 +105,11 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void preProcess() {
|
private void preProcess() {
|
||||||
for (DexNode dexNode : dexNodes) {
|
for (ClassNode cls : root.getClasses()) {
|
||||||
for (ClassNode cls : dexNode.getClasses()) {
|
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
|
||||||
doClass(cls);
|
}
|
||||||
}
|
for (ClassNode cls : root.getClasses()) {
|
||||||
|
preProcessClass(cls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,39 +118,164 @@ public class Deobfuscator {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
dumpAlias();
|
dumpAlias();
|
||||||
}
|
}
|
||||||
for (DexNode dexNode : dexNodes) {
|
for (ClassNode cls : root.getClasses()) {
|
||||||
for (ClassNode cls : dexNode.getClasses()) {
|
processClass(cls);
|
||||||
processClass(dexNode, 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() {
|
private OverridedMethodsNode getOverrideMethodsNode(Set<MethodInfo> overrideSet) {
|
||||||
deobfPresets.clear();
|
for (MethodInfo overrideMth : overrideSet) {
|
||||||
clsMap.clear();
|
OverridedMethodsNode node = ovrdMap.get(overrideMth);
|
||||||
fldMap.clear();
|
if (node != null) {
|
||||||
mthMap.clear();
|
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();
|
ClassInfo clsInfo = cls.getClassInfo();
|
||||||
String fullName = getClassFullName(clsInfo);
|
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||||
if (!fullName.equals(clsInfo.getFullName())) {
|
if (deobfClsInfo != null) {
|
||||||
clsInfo.rename(dex, fullName);
|
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()) {
|
for (FieldNode field : cls.getFields()) {
|
||||||
FieldInfo fieldInfo = field.getFieldInfo();
|
if (field.contains(AFlag.DONT_RENAME)) {
|
||||||
String alias = getFieldAlias(field);
|
continue;
|
||||||
if (alias != null) {
|
|
||||||
fieldInfo.setAlias(alias);
|
|
||||||
}
|
}
|
||||||
|
renameField(field);
|
||||||
}
|
}
|
||||||
for (MethodNode mth : cls.getMethods()) {
|
for (MethodNode mth : cls.getMethods()) {
|
||||||
MethodInfo methodInfo = mth.getMethodInfo();
|
renameMethod(mth);
|
||||||
String alias = getMethodAlias(mth);
|
}
|
||||||
if (alias != null) {
|
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||||
methodInfo.setAlias(alias);
|
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 fullPkgName full package name
|
||||||
* @param create if {@code true} then will create all absent objects
|
* @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) {
|
private PackageNode getPackageNode(String fullPkgName, boolean create) {
|
||||||
if (fullPkgName.isEmpty() || fullPkgName.equals(CLASS_NAME_SEPARATOR)) {
|
if (fullPkgName.isEmpty() || fullPkgName.equals(CLASS_NAME_SEPARATOR)) {
|
||||||
@@ -183,22 +337,24 @@ public class Deobfuscator {
|
|||||||
return prefix + clsInfo.getShortName();
|
return prefix + clsInfo.getShortName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doClass(ClassNode cls) {
|
private void preProcessClass(ClassNode cls) {
|
||||||
ClassInfo classInfo = cls.getClassInfo();
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
String pkgFullName = classInfo.getPackage();
|
String pkgFullName = classInfo.getPackage();
|
||||||
PackageNode pkg = getPackageNode(pkgFullName, true);
|
PackageNode pkg = getPackageNode(pkgFullName, true);
|
||||||
doPkg(pkg, pkgFullName);
|
processPackageFull(pkg, pkgFullName);
|
||||||
|
|
||||||
String alias = deobfPresets.getForCls(classInfo);
|
String alias = deobfPresets.getForCls(classInfo);
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
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)) {
|
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||||
return;
|
preProcessClass(innerCls);
|
||||||
}
|
|
||||||
if (shouldRename(classInfo.getShortName())) {
|
|
||||||
makeClsAlias(cls);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,42 +363,148 @@ public class Deobfuscator {
|
|||||||
if (deobfClsInfo != null) {
|
if (deobfClsInfo != null) {
|
||||||
return deobfClsInfo.getAlias();
|
return deobfClsInfo.getAlias();
|
||||||
}
|
}
|
||||||
return makeClsAlias(cls);
|
return makeClsAlias(cls, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeClsAlias(ClassNode cls) {
|
public String getPkgAlias(ClassNode cls) {
|
||||||
ClassInfo classInfo = cls.getClassInfo();
|
ClassInfo classInfo = cls.getClassInfo();
|
||||||
String alias = getAliasFromSourceFile(cls);
|
PackageNode pkg;
|
||||||
if (alias == null) {
|
DeobfClsInfo deobfClsInfo = clsMap.get(classInfo);
|
||||||
String clsName = classInfo.getShortName();
|
if (deobfClsInfo != null) {
|
||||||
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
|
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));
|
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||||
return 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
|
@Nullable
|
||||||
private String getAliasFromSourceFile(ClassNode cls) {
|
private String getAliasFromSourceFile(ClassNode cls) {
|
||||||
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
|
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
|
||||||
if (sourceFileAttr == null) {
|
if (sourceFileAttr == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (cls.getClassInfo().isInner()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
String name = sourceFileAttr.getFileName();
|
String name = sourceFileAttr.getFileName();
|
||||||
if (name.endsWith(".java")) {
|
if (name.endsWith(".java")) {
|
||||||
name = name.substring(0, name.length() - ".java".length());
|
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)
|
if (!NameMapper.isValidAndPrintable(name)) {
|
||||||
&& !NameMapper.isReserved(name)) {
|
return null;
|
||||||
// TODO: check if no class with this name exists or already renamed
|
|
||||||
cls.remove(AType.SOURCE_FILE);
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
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
|
@Nullable
|
||||||
public String getFieldAlias(FieldNode field) {
|
private String getFieldAlias(FieldNode field) {
|
||||||
FieldInfo fieldInfo = field.getFieldInfo();
|
FieldInfo fieldInfo = field.getFieldInfo();
|
||||||
String alias = fldMap.get(fieldInfo);
|
String alias = fldMap.get(fieldInfo);
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
@@ -260,8 +522,11 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String getMethodAlias(MethodNode mth) {
|
private String getMethodAlias(MethodNode mth) {
|
||||||
MethodInfo methodInfo = mth.getMethodInfo();
|
MethodInfo methodInfo = mth.getMethodInfo();
|
||||||
|
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
String alias = mthMap.get(methodInfo);
|
String alias = mthMap.get(methodInfo);
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
return alias;
|
return alias;
|
||||||
@@ -269,6 +534,7 @@ public class Deobfuscator {
|
|||||||
alias = deobfPresets.getForMth(methodInfo);
|
alias = deobfPresets.getForMth(methodInfo);
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
mthMap.put(methodInfo, alias);
|
mthMap.put(methodInfo, alias);
|
||||||
|
methodInfo.setAliasFromPreset(true);
|
||||||
return alias;
|
return alias;
|
||||||
}
|
}
|
||||||
if (shouldRename(mth.getName())) {
|
if (shouldRename(mth.getName())) {
|
||||||
@@ -278,18 +544,18 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String makeFieldAlias(FieldNode field) {
|
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);
|
fldMap.put(field.getFieldInfo(), alias);
|
||||||
return alias;
|
return alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String makeMethodAlias(MethodNode mth) {
|
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);
|
mthMap.put(mth.getMethodInfo(), alias);
|
||||||
return alias;
|
return alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doPkg(PackageNode pkg, String fullName) {
|
private void processPackageFull(PackageNode pkg, String fullName) {
|
||||||
if (pkgSet.contains(fullName)) {
|
if (pkgSet.contains(fullName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -299,47 +565,50 @@ public class Deobfuscator {
|
|||||||
PackageNode parentPkg = pkg.getParentPackage();
|
PackageNode parentPkg = pkg.getParentPackage();
|
||||||
while (!parentPkg.getName().isEmpty()) {
|
while (!parentPkg.getName().isEmpty()) {
|
||||||
if (!parentPkg.hasAlias()) {
|
if (!parentPkg.hasAlias()) {
|
||||||
doPkg(parentPkg, parentPkg.getFullName());
|
processPackageFull(parentPkg, parentPkg.getFullName());
|
||||||
}
|
}
|
||||||
parentPkg = parentPkg.getParentPackage();
|
parentPkg = parentPkg.getParentPackage();
|
||||||
}
|
}
|
||||||
|
|
||||||
final String pkgName = pkg.getName();
|
if (!pkg.hasAlias()) {
|
||||||
if (!pkg.hasAlias() && shouldRename(pkgName)) {
|
String pkgName = pkg.getName();
|
||||||
final String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
|
if ((args.isDeobfuscationOn() && shouldRename(pkgName))
|
||||||
pkg.setAlias(pkgAlias);
|
|| (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) {
|
private boolean shouldRename(String s) {
|
||||||
return s.length() > maxLength
|
int len = s.length();
|
||||||
|| s.length() < minLength
|
return len < minLength || len > maxLength;
|
||||||
|| NameMapper.isReserved(s)
|
|
||||||
|| !NameMapper.isAllCharsPrintable(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String makeName(String name) {
|
private String prepareNamePart(String name) {
|
||||||
if (name.length() > maxLength) {
|
if (name.length() > maxLength) {
|
||||||
return "x" + Integer.toHexString(name.hashCode());
|
return 'x' + Integer.toHexString(name.hashCode());
|
||||||
}
|
}
|
||||||
if (NameMapper.isReserved(name)) {
|
return NameMapper.removeInvalidCharsMiddle(name);
|
||||||
return name;
|
|
||||||
}
|
|
||||||
if (!NameMapper.isAllCharsPrintable(name)) {
|
|
||||||
return removeInvalidChars(name);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String removeInvalidChars(String name) {
|
private String prepareNameFull(String name, String prefix) {
|
||||||
StringBuilder sb = new StringBuilder();
|
if (name.length() > maxLength) {
|
||||||
for (int i = 0; i < name.length(); i++) {
|
return makeHashName(name, prefix);
|
||||||
int ch = name.charAt(i);
|
|
||||||
if (NameMapper.isPrintableChar(ch)) {
|
|
||||||
sb.append((char) ch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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) {
|
private void dumpClassAlias(ClassNode cls) {
|
||||||
@@ -355,15 +624,13 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void dumpAlias() {
|
private void dumpAlias() {
|
||||||
for (DexNode dexNode : dexNodes) {
|
for (ClassNode cls : root.getClasses()) {
|
||||||
for (ClassNode cls : dexNode.getClasses()) {
|
dumpClassAlias(cls);
|
||||||
dumpClassAlias(cls);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPackageName(String packageName) {
|
private String getPackageName(String packageName) {
|
||||||
final PackageNode pkg = getPackageNode(packageName, false);
|
PackageNode pkg = getPackageNode(packageName, false);
|
||||||
if (pkg != null) {
|
if (pkg != null) {
|
||||||
return pkg.getFullAlias();
|
return pkg.getFullAlias();
|
||||||
}
|
}
|
||||||
@@ -371,7 +638,7 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getClassName(ClassInfo clsInfo) {
|
private String getClassName(ClassInfo clsInfo) {
|
||||||
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||||
if (deobfClsInfo != null) {
|
if (deobfClsInfo != null) {
|
||||||
return deobfClsInfo.makeNameWithoutPkg();
|
return deobfClsInfo.makeNameWithoutPkg();
|
||||||
}
|
}
|
||||||
@@ -379,11 +646,8 @@ public class Deobfuscator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getClassFullName(ClassNode cls) {
|
private String getClassFullName(ClassNode cls) {
|
||||||
return getClassFullName(cls.getClassInfo());
|
ClassInfo clsInfo = cls.getClassInfo();
|
||||||
}
|
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||||
|
|
||||||
private String getClassFullName(ClassInfo clsInfo) {
|
|
||||||
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
|
||||||
if (deobfClsInfo != null) {
|
if (deobfClsInfo != null) {
|
||||||
return deobfClsInfo.getFullName();
|
return deobfClsInfo.getFullName();
|
||||||
}
|
}
|
||||||
@@ -405,4 +669,27 @@ public class Deobfuscator {
|
|||||||
public PackageNode getRootPackage() {
|
public PackageNode getRootPackage() {
|
||||||
return rootPackage;
|
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.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static jadx.core.utils.StringUtils.notEmpty;
|
||||||
|
|
||||||
public class NameMapper {
|
public class NameMapper {
|
||||||
|
|
||||||
private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
|
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(
|
private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile(
|
||||||
"(" + VALID_JAVA_IDENTIFIER + "\\.)*" + VALID_JAVA_IDENTIFIER);
|
"(" + VALID_JAVA_IDENTIFIER + "\\.)*" + VALID_JAVA_IDENTIFIER);
|
||||||
|
|
||||||
private static final Set<String> RESERVED_NAMES = new HashSet<String>(
|
private static final Set<String> RESERVED_NAMES = new HashSet<>(
|
||||||
Arrays.asList(new String[]{
|
Arrays.asList(
|
||||||
"abstract",
|
"abstract",
|
||||||
"assert",
|
"assert",
|
||||||
"boolean",
|
"boolean",
|
||||||
@@ -67,20 +69,34 @@ public class NameMapper {
|
|||||||
"try",
|
"try",
|
||||||
"void",
|
"void",
|
||||||
"volatile",
|
"volatile",
|
||||||
"while",
|
"while"));
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
public static boolean isReserved(String str) {
|
public static boolean isReserved(String str) {
|
||||||
return RESERVED_NAMES.contains(str);
|
return RESERVED_NAMES.contains(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValidIdentifier(String 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) {
|
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) {
|
public static boolean isPrintableChar(int c) {
|
||||||
@@ -96,4 +112,54 @@ public class NameMapper {
|
|||||||
}
|
}
|
||||||
return true;
|
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;
|
package jadx.core.deobf;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
public class PackageNode {
|
public class PackageNode {
|
||||||
|
|
||||||
@@ -29,15 +30,18 @@ public class PackageNode {
|
|||||||
|
|
||||||
public String getFullName() {
|
public String getFullName() {
|
||||||
if (cachedPackageFullName == null) {
|
if (cachedPackageFullName == null) {
|
||||||
Stack<PackageNode> pp = getParentPackages();
|
Deque<PackageNode> pp = getParentPackages();
|
||||||
|
if (pp.isEmpty()) {
|
||||||
StringBuilder result = new StringBuilder();
|
cachedPackageFullName = "";
|
||||||
result.append(pp.pop().getName());
|
} else {
|
||||||
while (pp.size() > 0) {
|
StringBuilder result = new StringBuilder();
|
||||||
result.append(SEPARATOR_CHAR);
|
|
||||||
result.append(pp.pop().getName());
|
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;
|
return cachedPackageFullName;
|
||||||
}
|
}
|
||||||
@@ -57,14 +61,29 @@ public class PackageNode {
|
|||||||
return packageAlias != null;
|
return packageAlias != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasAnyAlias() {
|
||||||
|
if (hasAlias()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (parentPackage != this) {
|
||||||
|
return parentPackage.hasAnyAlias();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public String getFullAlias() {
|
public String getFullAlias() {
|
||||||
if (cachedPackageFullAlias == null) {
|
if (cachedPackageFullAlias == null) {
|
||||||
Stack<PackageNode> pp = getParentPackages();
|
Deque<PackageNode> pp = getParentPackages();
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
result.append(pp.pop().getAlias());
|
|
||||||
while (pp.size() > 0) {
|
if (!pp.isEmpty()) {
|
||||||
result.append(SEPARATOR_CHAR);
|
|
||||||
result.append(pp.pop().getAlias());
|
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();
|
cachedPackageFullAlias = result.toString();
|
||||||
}
|
}
|
||||||
@@ -81,7 +100,7 @@ public class PackageNode {
|
|||||||
|
|
||||||
public void addInnerPackage(PackageNode pkg) {
|
public void addInnerPackage(PackageNode pkg) {
|
||||||
if (innerPackages.isEmpty()) {
|
if (innerPackages.isEmpty()) {
|
||||||
innerPackages = new ArrayList<PackageNode>();
|
innerPackages = new ArrayList<>();
|
||||||
}
|
}
|
||||||
innerPackages.add(pkg);
|
innerPackages.add(pkg);
|
||||||
pkg.parentPackage = this;
|
pkg.parentPackage = this;
|
||||||
@@ -109,17 +128,21 @@ public class PackageNode {
|
|||||||
*
|
*
|
||||||
* @return stack with parent packages
|
* @return stack with parent packages
|
||||||
*/
|
*/
|
||||||
private Stack<PackageNode> getParentPackages() {
|
private Deque<PackageNode> getParentPackages() {
|
||||||
Stack<PackageNode> pp = new Stack<PackageNode>();
|
Deque<PackageNode> pp = new ArrayDeque<>();
|
||||||
|
|
||||||
PackageNode currentP = this;
|
PackageNode currentPkg = this;
|
||||||
PackageNode parentP = currentP.getParentPackage();
|
PackageNode parentPkg = currentPkg.getParentPackage();
|
||||||
|
while (currentPkg != parentPkg) {
|
||||||
while (currentP != parentP) {
|
pp.push(currentPkg);
|
||||||
pp.push(currentP);
|
currentPkg = parentPkg;
|
||||||
currentP = parentP;
|
parentPkg = currentPkg.getParentPackage();
|
||||||
parentP = currentP.getParentPackage();
|
|
||||||
}
|
}
|
||||||
return pp;
|
return pp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return packageAlias;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.core.dex.attributes;
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
public enum AFlag {
|
public enum AFlag {
|
||||||
|
MTH_ENTER_BLOCK,
|
||||||
TRY_ENTER,
|
TRY_ENTER,
|
||||||
TRY_LEAVE,
|
TRY_LEAVE,
|
||||||
|
|
||||||
@@ -12,19 +13,46 @@ public enum AFlag {
|
|||||||
RETURN, // block contains only return instruction
|
RETURN, // block contains only return instruction
|
||||||
ORIG_RETURN,
|
ORIG_RETURN,
|
||||||
|
|
||||||
DECLARE_VAR,
|
|
||||||
DONT_WRAP,
|
DONT_WRAP,
|
||||||
|
|
||||||
DONT_SHRINK,
|
|
||||||
DONT_INLINE,
|
DONT_INLINE,
|
||||||
DONT_GENERATE,
|
DONT_INLINE_CONST,
|
||||||
SKIP,
|
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||||
REMOVE,
|
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_FIRST_ARG,
|
||||||
|
SKIP_ARG, // skip argument in invoke call
|
||||||
ANONYMOUS_CONSTRUCTOR,
|
ANONYMOUS_CONSTRUCTOR,
|
||||||
ANONYMOUS_CLASS,
|
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,
|
ELSE_IF_CHAIN,
|
||||||
|
|
||||||
WRAPPED,
|
WRAPPED,
|
||||||
@@ -32,5 +60,23 @@ public enum AFlag {
|
|||||||
|
|
||||||
FALL_THROUGH,
|
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
|
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;
|
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.AnnotationsList;
|
||||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
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.EnumClassAttr;
|
||||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
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.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.JumpInfo;
|
||||||
|
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
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.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.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.CatchAttr;
|
||||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||||
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||||
@@ -26,26 +38,61 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
|
|||||||
*
|
*
|
||||||
* @param <T> attribute class implementation
|
* @param <T> attribute class implementation
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||||
public class AType<T extends IAttribute> {
|
public class AType<T extends IAttribute> {
|
||||||
|
|
||||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
|
// class, method, field
|
||||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
|
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>();
|
// class, method
|
||||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
|
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
|
||||||
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
|
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
|
||||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
|
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
|
||||||
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
|
|
||||||
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
|
// class
|
||||||
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
|
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
|
||||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
|
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
|
||||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
|
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
|
||||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
|
|
||||||
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
|
// field
|
||||||
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
|
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
|
||||||
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
|
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
|
||||||
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
|
|
||||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
|
// method
|
||||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
|
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<IgnoreEdgeAttr>();
|
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;
|
package jadx.core.dex.attributes;
|
||||||
|
|
||||||
import jadx.core.utils.Utils;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import jadx.core.codegen.CodeWriter;
|
||||||
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public class AttrList<T> implements IAttribute {
|
public class AttrList<T> implements IAttribute {
|
||||||
|
|
||||||
private final AType<AttrList<T>> type;
|
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) {
|
public AttrList(AType<AttrList<T>> type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -25,6 +26,6 @@ public class AttrList<T> implements IAttribute {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
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