Compare commits
984 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 3782aa7d0a | |||
| d5740c1b08 | |||
| 3357979cc9 | |||
| 2f548dd9eb | |||
| f715d6ce68 | |||
| 350b605400 | |||
| 6a99d00487 | |||
| f87bf3f14d | |||
| 87347c0a04 | |||
| 217737b3e8 | |||
| efd8bd13da | |||
| 051bb63a81 | |||
| e4f4de6c8d | |||
| e6aa85e01d | |||
| cc4d94321e | |||
| c1292dff75 | |||
| 1d81cab4a1 | |||
| 2815cef1bb | |||
| d4523c4e53 | |||
| 5d894b6150 | |||
| 2eddbb9119 | |||
| a2513240ff | |||
| 0d509f94b7 | |||
| e4fbbcf2d6 | |||
| 9afacf72f8 | |||
| 78a7e65a2d | |||
| 3314de8dde | |||
| 8dab9b83be | |||
| 7b264ef2be | |||
| 5a6600f748 | |||
| 14ed0c3a3d | |||
| 229d78f1ef | |||
| f770e4ef42 | |||
| 66aa2f8f2a | |||
| 99d831c498 | |||
| a532287ddf | |||
| 7844e554aa | |||
| 10de4ff490 | |||
| eed65421ea | |||
| 7accc6e516 | |||
| fa8f9ccfaa | |||
| 8a264ca321 | |||
| f366eac7eb | |||
| 46d3992b41 | |||
| 164123f542 | |||
| 72c301dc54 | |||
| e8fd1e1dc7 | |||
| 2b7f8931a4 | |||
| ec3b71e5b6 | |||
| f7303881aa | |||
| 1b98be0b0a | |||
| e5b84d942e | |||
| 22e9ac22ba | |||
| 8a6cdec796 | |||
| c5c4499a55 | |||
| 30138f7a38 | |||
| 883429fa47 | |||
| 380ee75d9a | |||
| 99d9814083 | |||
| 141398aeac | |||
| 07cef6fd62 | |||
| aac041f960 | |||
| 6ef1600041 | |||
| 733836ea2d | |||
| b4767626d9 | |||
| 84edfac8fa | |||
| 69252ce721 | |||
| df1152516a | |||
| 02f9c25f52 | |||
| 7fb3988173 | |||
| a50352780b | |||
| ff093aeebb | |||
| aa691af664 | |||
| e0ffb01852 | |||
| 53be92c616 | |||
| 5f8f454b55 | |||
| 3700ecb717 | |||
| 811b0e7f30 | |||
| 08ea61f4df | |||
| 1d5368f5a2 | |||
| 90fb95e785 | |||
| 0f97f07461 | |||
| 7fe6b842a6 | |||
| 02a97bcb3a | |||
| fd4289aa64 | |||
| 716db8b964 | |||
| b55975a35a | |||
| 4cb34394b4 | |||
| aacb83290e | |||
| ddab4c269d | |||
| 6ddb0036fa | |||
| 0f7ca8cea4 | |||
| c4367e25a9 | |||
| e081aadd27 | |||
| 2bacab7dc0 | |||
| 824db6be2b | |||
| 2fdb26146b | |||
| b87d1a7fe1 | |||
| c242a62bcc | |||
| 6c91bce663 | |||
| 7fd46633a3 | |||
| 3c425990f6 | |||
| 55f16cc3ec | |||
| e01789bb0d | |||
| e3696af8ea | |||
| a26d7b5a8b | |||
| c4fe9150bf | |||
| ffc642048e | |||
| 8de6190a81 | |||
| d6e2c69202 | |||
| 1a85fa8e3c | |||
| c7b8508c6f | |||
| c35f6e2543 | |||
| 8052a90d04 | |||
| 3d20d7d330 | |||
| 5e722c6827 | |||
| 10198bc87f | |||
| a6b4043e8c | |||
| 9cea0163fa | |||
| 577176dd31 | |||
| a135eb44f3 | |||
| 252ed0e1e4 | |||
| fcb120a3ed | |||
| 988628a2e7 | |||
| c24cdf5cc1 | |||
| d748e004d2 | |||
| 380b73d1b9 | |||
| ef85e29a9b | |||
| 1daf5d1090 | |||
| 9d2c0e4aea | |||
| 7277ebb9c4 | |||
| c18074f6aa | |||
| 8a706193e7 | |||
| 9d77f5f5df | |||
| 6951d0e646 | |||
| 73dd55eac2 | |||
| b5a9389cc6 | |||
| d905c96fbe | |||
| 03f03f85af | |||
| 2b00a8a406 | |||
| f31c2dcd21 | |||
| 7699cfac02 | |||
| 5c48a457b4 | |||
| b5f439e1aa | |||
| 202fe5a0a9 | |||
| 68ccf57bd4 | |||
| 84970759d8 | |||
| 53cac58ebe | |||
| adc32ed319 | |||
| 7f0815a7b2 | |||
| 68f5565b63 | |||
| c552fb857d | |||
| 8a4ec47b92 | |||
| d281126337 | |||
| 4fb6ada5ec | |||
| ab924faa1e | |||
| b12b129af7 | |||
| 017c6b4d42 | |||
| d55cd5fbb4 | |||
| 13a6b1c8c6 | |||
| 0bc37e5d32 | |||
| 46c8572887 | |||
| e6b919007c | |||
| ac5a6096bb | |||
| db527fbbda | |||
| 8f201f1fee | |||
| d1e0762c12 | |||
| 010ae99c69 | |||
| a4632d6e86 | |||
| 2a3162f869 | |||
| 2063fd0742 | |||
| 128fe8a839 | |||
| 2478fc3a1b | |||
| 5a68d3bef7 | |||
| 195eeceb62 | |||
| ec8309af49 | |||
| 627a4dc802 | |||
| e2018535ef | |||
| ee56610f06 | |||
| fb9ff7748a | |||
| cdfb46d9d3 | |||
| 5545a94a9e | |||
| 9e811d959b | |||
| 957d5394d2 | |||
| 95afe1219e | |||
| 07937f1d71 | |||
| 671be0af0a | |||
| 7e9278f992 | |||
| 9194441c47 | |||
| 4f307c0085 | |||
| 3bdda55102 | |||
| b657b0fb1f | |||
| 4935ae6da5 | |||
| 72a50eae43 | |||
| fa37b90cff | |||
| 052a8db606 | |||
| 88ccba166e | |||
| 58998089a6 | |||
| f0a73b329e | |||
| c97678a477 | |||
| 2ad739275f | |||
| caad78885d | |||
| a234227b9f | |||
| 16f736e773 | |||
| 1fe24ad11d | |||
| 33c5e0827a | |||
| cbd36aeb8f | |||
| 2963bb3f41 | |||
| 09a6ceac63 | |||
| 75d8a01cab | |||
| 0968f75e9a | |||
| bc0db88afa | |||
| 5f11f12d0c | |||
| 2d18950542 | |||
| 50d314445a | |||
| f8d57d9265 | |||
| ebbe6db378 | |||
| 543cad3a23 | |||
| 41cc83dbf6 | |||
| ce7101be88 | |||
| 0a241e3a9c | |||
| 37857e88ea | |||
| 6fbcf46a8b | |||
| a36bc8f29a | |||
| 813b7bca6e | |||
| e2945f2a42 | |||
| eaf623a560 | |||
| 26aa504590 | |||
| e4dde3f4b6 | |||
| 9c90699c40 | |||
| b67cd50e8a | |||
| d2acaa03f5 | |||
| f2aa4cd10b | |||
| b940b99e75 | |||
| 868e0706ea | |||
| 324f544ba2 | |||
| 0a1981f90e | |||
| 0a36bfb088 | |||
| 0d94af099b | |||
| 4a6115ed64 | |||
| 42eb319751 | |||
| 343bddc6ad | |||
| 632a742ea9 | |||
| 08c9d1228a | |||
| 11d8b28fb4 | |||
| 12b6371209 | |||
| 24d22aaafb | |||
| ebf7822628 | |||
| 7669fa1582 | |||
| e49ba61917 | |||
| 96db1c2479 | |||
| 7abdb41a9e | |||
| 14f6d2f3b0 | |||
| 4e4b4975ad | |||
| 93fafcf886 | |||
| 82cc88a1b9 | |||
| 5c94e0bccc | |||
| 18a1788d2d | |||
| d0aa19118b | |||
| 039f6eebda | |||
| 8a464e8274 | |||
| 066b5a895d | |||
| 4c4af7928e | |||
| a0d8d9fcc6 | |||
| a2142b2ff8 | |||
| 5ed5ec5f7d | |||
| 95795620d5 | |||
| 890c0a9909 | |||
| b73cb40690 | |||
| ca448fc4d8 | |||
| 7a51c0d087 | |||
| 8762125bbf | |||
| 3d0c6e49ab | |||
| 03da35b29e | |||
| 3ccab60f43 | |||
| ed64b8c121 | |||
| 2a60ac47fe | |||
| 9cd72fe1e9 | |||
| 476b2c3735 | |||
| 5258c8363a | |||
| eb6d145dca | |||
| 63c003a02d | |||
| 5557fd814b | |||
| b1dc26ee06 | |||
| 56c0a588de | |||
| 47d65fcd87 | |||
| 85ab095630 | |||
| 1b5f0f6af6 | |||
| 2cf28eb2e7 | |||
| 2b300341a0 | |||
| 01fabca358 | |||
| 4ace552a27 | |||
| b61daaed33 | |||
| c6f0c89cf6 | |||
| 3c84975a09 | |||
| bb4ef4f0a2 | |||
| fd00330e6e | |||
| d10efec1ab | |||
| 3f08c99f19 | |||
| e3606d1b53 | |||
| ab593e3cd9 | |||
| 4a0aacf104 | |||
| 917cf20d37 | |||
| dabaeed8df | |||
| 4923b36e70 | |||
| ebf06fde65 | |||
| 438b3b50d9 | |||
| 6bac5c162e | |||
| 5cbf71bde6 | |||
| a85d382e89 | |||
| 4caa58f5fd | |||
| 43913d47ec | |||
| 9f51cabf69 | |||
| 1c60e5e315 | |||
| a9290f3131 | |||
| e46dfc555e | |||
| e54b764588 | |||
| 37f03bcf9e | |||
| 1d0f23dfbb | |||
| 30355cc9d6 | |||
| ed67f8e118 | |||
| 4531256005 | |||
| 662ebb6451 | |||
| 4a63f52259 | |||
| c416f77e99 | |||
| fde431d131 | |||
| 272e0d3754 | |||
| b44a1e3a4f | |||
| b18dabee15 | |||
| b6befbdcf2 | |||
| 2cfc208aa9 | |||
| 132b8d0618 | |||
| 5dc4c28da5 | |||
| 7342ae18a6 | |||
| eafe080c41 | |||
| 4f61ddd4b7 | |||
| 86b0458673 | |||
| 36cfc9d189 | |||
| b2f189b572 | |||
| eec524ad85 | |||
| d94087b939 | |||
| 1ba19d3600 | |||
| 07402ba4c0 | |||
| d60698206e | |||
| c59b65e71c | |||
| bd4c61d300 | |||
| 00a6b6efd2 | |||
| 04ac3b2eb7 | |||
| 6bc2d3321c | |||
| c95211925e | |||
| 95e9da36c5 | |||
| 9bf7270bf3 | |||
| a99e0e9618 | |||
| 01c4706013 | |||
| 89c7b9a848 | |||
| 1358a05a74 | |||
| 1b0a8990f7 | |||
| 4edfffae27 | |||
| cde8d72510 | |||
| d7ce0245f6 | |||
| 49c5ceb06e | |||
| 4c03a4245b | |||
| 4454e013c4 | |||
| 1e7546f4a3 | |||
| 7742d34111 | |||
| a413aaf140 | |||
| e94396532e | |||
| cc1be673e7 | |||
| f9e87d4da0 | |||
| ab8fa23fc3 | |||
| 7985466213 | |||
| e92ed48502 | |||
| c508e72c19 | |||
| 940de24099 | |||
| 6ddb71e21f | |||
| d0f120c314 | |||
| 54f4c6d2cb | |||
| 1f21760bbe | |||
| 67eb55a95d | |||
| fa097cc6b2 | |||
| 34222dae0a | |||
| 3a62d04376 | |||
| ca2c935f65 | |||
| ddf2174cae | |||
| 7096c38299 | |||
| c4cdd8514d | |||
| 25b2c8fe5b | |||
| 36da79feb8 | |||
| 571b5590ac | |||
| 7eb5defc2a | |||
| ce7d6f0156 | |||
| cbbb73355b | |||
| f51d633707 | |||
| bca90c1f41 | |||
| 17c0fd21d2 | |||
| fb43d716d9 | |||
| 3598a1279c |
@@ -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,23 @@
|
||||
---
|
||||
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)
|
||||
- [ ] search existing issues by exception message
|
||||
(for example `JadxRuntimeException: Can't find immediate dominator for block`)
|
||||
- [ ] check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
||||
|
||||
**Describe error**
|
||||
- provide full name of method or class with error
|
||||
- provide full java stacktrace
|
||||
|
||||
**Note**: no need to copy method fallback code (commented pseudocode)
|
||||
- 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 :)
|
||||
+12
-2
@@ -7,17 +7,27 @@
|
||||
.idea/
|
||||
out/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.attach_pid*
|
||||
*.hprof
|
||||
|
||||
**/.DS_Store
|
||||
|
||||
bin/
|
||||
target/
|
||||
build/
|
||||
classes/
|
||||
idea/
|
||||
.gradle/
|
||||
node_modules/
|
||||
|
||||
jadx-output/
|
||||
*-tmp/
|
||||
|
||||
*.dex
|
||||
*.jar
|
||||
*.class
|
||||
*.dump
|
||||
*.log
|
||||
*.cfg
|
||||
|
||||
*.orig
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
variables:
|
||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
||||
TERM: "dumb"
|
||||
|
||||
before_script:
|
||||
- chmod +x gradlew
|
||||
|
||||
stages:
|
||||
- test
|
||||
- check
|
||||
|
||||
java-8:
|
||||
stage: test
|
||||
image: openjdk:8
|
||||
script: ./gradlew clean build
|
||||
|
||||
java-11:
|
||||
stage: test
|
||||
image: openjdk:11
|
||||
script: ./gradlew clean build
|
||||
|
||||
check:
|
||||
stage: check
|
||||
image: openjdk:8
|
||||
script:
|
||||
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
|
||||
- export JADX_VERSION="${JADX_LAST_TAG:1}-dev-$(git rev-parse --short HEAD)"
|
||||
- ./gradlew clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN -Dsonar.branch.name=dev
|
||||
- ./gradlew clean dist
|
||||
artifacts:
|
||||
paths:
|
||||
- build/jadx*.zip
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "jadx-test-app/test-app"]
|
||||
path = jadx-test-app/test-app
|
||||
url = git://github.com/skylot/jadx-test-app.git
|
||||
@@ -0,0 +1,35 @@
|
||||
branch: 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
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
language: java
|
||||
sudo: false
|
||||
dist: trusty
|
||||
|
||||
# don't build on tag push
|
||||
if: tag IS blank
|
||||
|
||||
git:
|
||||
depth: false
|
||||
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
|
||||
# override install to skip 'gradle assemble'
|
||||
install: true
|
||||
|
||||
env:
|
||||
global:
|
||||
- TERM=dumb
|
||||
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
|
||||
|
||||
jdk:
|
||||
- openjdk8
|
||||
- oraclejdk8
|
||||
- openjdk11
|
||||
|
||||
script: ./gradlew clean build
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- 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
|
||||
jdk: openjdk8
|
||||
if: branch = release AND repo = env(MAIN_REPO) AND type = push
|
||||
script: bash scripts/travis-release.sh
|
||||
@@ -0,0 +1,7 @@
|
||||
# Contribution
|
||||
|
||||
To support this project you can:
|
||||
- Post thoughts about new features/optimizations that important to you
|
||||
- Submit bug using one of following patterns:
|
||||
* Java code examples which decompiles incorrectly
|
||||
* Error log and link to _public available_ apk file or app page on Google play
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
||||
@@ -1,8 +1,8 @@
|
||||
The majority of jadx is written and copyrighted by me (Skylot)
|
||||
and released under the Apache 2.0 license:
|
||||
and released under the Apache 2.0 license (see LICENSE file for full license text):
|
||||
|
||||
*******************************************************************************
|
||||
Copyright 2013 Skylot
|
||||
Copyright 2015, Skylot
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,8 @@ limitations under the License.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Various portions of the code including dx library are taken from
|
||||
the Android Open Source Project, and are used in accordance with
|
||||
Various portions of the code including dx library are taken from
|
||||
the Android Open Source Project, and are used in accordance with
|
||||
the following license:
|
||||
|
||||
*******************************************************************************
|
||||
@@ -74,10 +74,10 @@ Copyright (c) 2004-2011 QOS.ch
|
||||
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
|
||||
@@ -92,15 +92,122 @@ Logback source code and binaries are dual-licensed under the EPL v1.0 and the LG
|
||||
|
||||
*******************************************************************************
|
||||
Logback: the reliable, generic, fast and flexible logging framework.
|
||||
Copyright (C) 1999-2012, QOS.ch. All rights reserved.
|
||||
Copyright (C) 1999-2012, QOS.ch. All rights reserved.
|
||||
|
||||
This program and the accompanying materials are dual-licensed under
|
||||
either the terms of the Eclipse Public License v1.0 as published by
|
||||
the Eclipse Foundation
|
||||
|
||||
|
||||
or (per the licensee's choosing)
|
||||
|
||||
|
||||
under the terms of the GNU Lesser General Public License version 2.1
|
||||
as published by the Free Software Foundation.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
ASM library:
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2000-2011 INRIA, France Telecom
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holders nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
|
||||
Jadx-gui components
|
||||
===================
|
||||
|
||||
RSyntaxTextArea library (https://github.com/bobbylight/RSyntaxTextArea)
|
||||
licensed under modified BSD license:
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2012, Robert Futrell
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the author nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Concurrent Trees (https://code.google.com/p/concurrent-trees/)
|
||||
licenced under Apache License 2.0:
|
||||
|
||||
*******************************************************************************
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Image Viewer (https://github.com/kazocsaba/imageviewer)
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2008-2012 Kazó Csaba
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*******************************************************************************
|
||||
|
||||
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
|
||||
|
||||
Icons copied from several places:
|
||||
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
||||
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed
|
||||
under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
|
||||
|
||||
@@ -1,46 +1,111 @@
|
||||
## JADX [](https://buildhive.cloudbees.com/job/skylot/job/jadx/)
|
||||
## JADX
|
||||
|
||||
[](https://travis-ci.org/skylot/jadx)
|
||||
[](https://codecov.io/gh/skylot/jadx)
|
||||
[](https://sonarcloud.io/dashboard?id=jadx)
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
[](https://github.com/semantic-release/semantic-release)
|
||||
|
||||
**jadx** - Dex to Java decompiler
|
||||
|
||||
Command line tool for produce Java sources from Android Dex and Jar files
|
||||
Command line and GUI tools for produce Java source code from Android Dex and Apk files
|
||||
|
||||

|
||||
|
||||
|
||||
### Downloads
|
||||
Latest version available at
|
||||
[sourceforge](http://sourceforge.net/projects/jadx/files/)
|
||||
or
|
||||
[bintray](http://bintray.com/pkg/show/general/skylot/jadx/jadx-cli)
|
||||
- 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)
|
||||
|
||||
### Build
|
||||
After download unpack zip file go to `bin` directory and run:
|
||||
- `jadx` - command line version
|
||||
- `jadx-gui` - graphical version
|
||||
|
||||
On Windows run `.bat` files with double-click\
|
||||
**Note:** ensure you have installed Java 8 64-bit version
|
||||
|
||||
|
||||
### Related projects:
|
||||
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
|
||||
|
||||
|
||||
### Building jadx from source
|
||||
JDK 8 or higher must be installed:
|
||||
|
||||
git clone https://github.com/skylot/jadx.git
|
||||
cd jadx
|
||||
./gradlew build
|
||||
|
||||
./gradlew dist
|
||||
|
||||
(on Windows, use `gradlew.bat` instead of `./gradlew`)
|
||||
|
||||
Scripts for run jadx will be placed in `build/install/jadx/bin`
|
||||
and also packed to `build/distributions/jadx-<version>.zip`
|
||||
Scripts for run jadx will be placed in `build/jadx/bin`
|
||||
and also packed to `build/jadx-<version>.zip`
|
||||
|
||||
### macOS
|
||||
You can install using brew:
|
||||
|
||||
brew install jadx
|
||||
|
||||
### Run
|
||||
Run **jadx** on itself:
|
||||
|
||||
cd build/install/jadx/
|
||||
bin/jadx -d out lib/jadx-*.jar
|
||||
cd build/jadx/
|
||||
bin/jadx -d out lib/jadx-core-*.jar
|
||||
# or
|
||||
bin/jadx-gui lib/jadx-core-*.jar
|
||||
|
||||
|
||||
### Usage
|
||||
```
|
||||
jadx [options] <input files> (.dex, .apk or .jar)
|
||||
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
|
||||
options:
|
||||
-d, --output-dir - output directory
|
||||
-j, --threads-count - processing threads count
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
--cfg - save methods control flow graph
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-v, --verbose - verbose output
|
||||
-h, --help - print this help
|
||||
-d, --output-dir - output directory
|
||||
-ds, --output-dir-src - output directory for sources
|
||||
-dr, --output-dir-res - output directory for resources
|
||||
-j, --threads-count - processing threads count
|
||||
-r, --no-res - do not decode resources
|
||||
-s, --no-src - do not decompile source code
|
||||
--single-class - decompile a single class
|
||||
--output-format - can be 'java' or 'json' (default: java)
|
||||
-e, --export-gradle - save as android gradle project
|
||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||
--no-imports - disable use of imports, always write entire package name
|
||||
--no-debug-info - disable debug info
|
||||
--no-inline-anonymous - disable anonymous classes inline
|
||||
--no-replace-consts - don't replace constant value with matching constant field
|
||||
--escape-unicode - escape non latin characters in strings (with \u)
|
||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||
--deobf - activate deobfuscation
|
||||
--deobf-min - min length of name, renamed if shorter (default: 3)
|
||||
--deobf-max - max length of name, renamed if longer (default: 64)
|
||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all'
|
||||
--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
|
||||
--version - print jadx version
|
||||
-h, --help - print this help
|
||||
Example:
|
||||
jadx -d out classes.dex
|
||||
jadx --rename-flags "none" classes.dex
|
||||
jadx --rename-flags "valid,printable" classes.dex
|
||||
```
|
||||
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
||||
|
||||
### Troubleshooting
|
||||
##### Out of memory error:
|
||||
- Reduce processing threads count (`-j` option)
|
||||
- Increase maximum java heap size:
|
||||
* command line (example for linux):
|
||||
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
|
||||
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
|
||||
`DEFAULT_JVM_OPTS="-Xmx2500M"`
|
||||
|
||||
---------------------------------------
|
||||
*Licensed under the Apache 2.0 License*
|
||||
|
||||
*Copyright 2013 by Skylot*
|
||||
*Copyright 2018 by Skylot*
|
||||
|
||||
+144
-69
@@ -1,94 +1,169 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'application'
|
||||
import com.diffplug.spotless.LineEnding
|
||||
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'idea'
|
||||
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
|
||||
version = file('version').readLines().get(0)
|
||||
|
||||
mainClassName = "jadx.Main"
|
||||
manifest.mainAttributes("jadx-version" : version)
|
||||
|
||||
project.ext {
|
||||
mainSamplesClass = "jadx.samples.RunTests"
|
||||
samplesJadxSrcDir = "${buildDir}/samples-jadx/src"
|
||||
plugins {
|
||||
id 'org.sonarqube' version '2.7'
|
||||
id 'com.github.ben-manes.versions' version '0.21.0'
|
||||
id "com.diffplug.gradle.spotless" version "3.21.1"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.android.tools:dx:1.7'
|
||||
compile 'com.beust:jcommander:1.30'
|
||||
compile 'org.slf4j:slf4j-api:1.7.5'
|
||||
compile 'ch.qos.logback:logback-classic:1.0.13'
|
||||
testCompile 'junit:junit:4.11'
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
version = jadxVersion
|
||||
println("jadx version: ${jadxVersion}")
|
||||
|
||||
allprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
version = jadxVersion
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
if (!"$it".contains(':jadx-samples:')) {
|
||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||
}
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
jar {
|
||||
version = jadxVersion
|
||||
manifest {
|
||||
mainAttributes('jadx-version': jadxVersion)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.26'
|
||||
|
||||
testCompile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
testCompile 'org.hamcrest:hamcrest-library:2.1'
|
||||
testCompile 'org.mockito:mockito-core:2.25.1'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.1'
|
||||
|
||||
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = "0.8.2"
|
||||
}
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
checkstyleMain {
|
||||
// exclude all sources in samples module
|
||||
exclude '**/samples/**'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
sonarqube {
|
||||
properties {
|
||||
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
|
||||
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
samples
|
||||
//TODO don't add to eclipse classpath
|
||||
samplesJadx {
|
||||
java {
|
||||
srcDir samplesJadxSrcDir
|
||||
output.classesDir "${buildDir}/samples-jadx/output"
|
||||
}
|
||||
}
|
||||
spotless {
|
||||
java {
|
||||
target fileTree(rootDir).matching {
|
||||
include 'jadx-cli/src/**/java/**/*.java'
|
||||
include 'jadx-core/src/**/java/**/*.java'
|
||||
include 'jadx-gui/src/**/java/**/*.java'
|
||||
}
|
||||
|
||||
importOrderFile 'config/code-formatter/eclipse.importorder'
|
||||
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
||||
removeUnusedImports()
|
||||
|
||||
lineEndings(LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
format 'misc', {
|
||||
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
|
||||
targetExclude "jadx-test-app/test-app/**", ".gradle/**", ".idea/**"
|
||||
|
||||
lineEndings(LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
}
|
||||
|
||||
task samplesRun(type: JavaExec, dependsOn: compileSamplesJava) {
|
||||
classpath = sourceSets.samples.output
|
||||
main = mainSamplesClass
|
||||
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 samplesJar(type: Jar, dependsOn: samplesRun) {
|
||||
baseName = 'samples'
|
||||
from sourceSets.samples.output
|
||||
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
|
||||
destinationDir file("$buildDir/jadx")
|
||||
['jadx-cli', 'jadx-gui'].each {
|
||||
from tasks.getByPath(":${it}:installDist").destinationDir
|
||||
}
|
||||
}
|
||||
|
||||
task samplesJadxCreate(type: JavaExec, dependsOn: [compileJava, samplesJar]) {
|
||||
classpath = sourceSets.main.output + configurations.compile
|
||||
main = mainClassName
|
||||
args = ['-d', samplesJadxSrcDir, samplesJar.archivePath]
|
||||
task pack(type: Zip, dependsOn: copyArtifacts) {
|
||||
destinationDir buildDir
|
||||
archiveName "jadx-${jadxVersion}.zip"
|
||||
from copyArtifacts.destinationDir
|
||||
}
|
||||
|
||||
compileSamplesJadxJava.dependsOn samplesJadxCreate
|
||||
|
||||
task samplesJadxRun(type: JavaExec, dependsOn: compileSamplesJadxJava) {
|
||||
classpath = sourceSets.samplesJadx.output
|
||||
main = mainSamplesClass
|
||||
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
|
||||
group 'jadx'
|
||||
description = 'Copy exe to build dir'
|
||||
destinationDir buildDir
|
||||
from tasks.getByPath('jadx-gui:createExe').outputs
|
||||
include '*.exe'
|
||||
}
|
||||
|
||||
task samples (dependsOn: samplesJadxRun) {
|
||||
task dist(dependsOn: [pack, copyExe]) {
|
||||
group 'jadx'
|
||||
description = 'Build jadx distribution zip'
|
||||
}
|
||||
|
||||
//check.dependsOn samples
|
||||
build.dependsOn distZip
|
||||
build.dependsOn installApp
|
||||
|
||||
startScripts {
|
||||
doLast {
|
||||
// increase default max heap size
|
||||
String var = 'DEFAULT_JVM_OPTS='
|
||||
String args = '-Xmx1400M'
|
||||
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
|
||||
windowsScript.text = windowsScript.text.replace(var, var + args)
|
||||
}
|
||||
task samples(dependsOn: 'jadx-samples:samples') {
|
||||
group 'jadx'
|
||||
}
|
||||
|
||||
applicationDistribution.with {
|
||||
into('') {
|
||||
from '.'
|
||||
include 'README.md'
|
||||
include 'NOTICE'
|
||||
}
|
||||
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
|
||||
group 'jadx'
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '1.6'
|
||||
task cleanBuildDir(type: Delete) {
|
||||
group 'jadx'
|
||||
delete buildDir
|
||||
}
|
||||
|
||||
test.dependsOn(samples)
|
||||
|
||||
clean.dependsOn(cleanBuildDir)
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<?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"/>
|
||||
<module name="MissingOverride"/>
|
||||
<!-- <module name="MissingDeprecated"/> -->
|
||||
|
||||
<module name="ModifierOrder"/>
|
||||
<!-- <module name="RedundantModifier"/> -->
|
||||
<!-- <module name="ParameterNumber"/> -->
|
||||
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="DefaultComesLast"/>
|
||||
<module name="EqualsHashCode"/>
|
||||
<module name="FallThrough"/>
|
||||
<!-- <module name="IllegalCatch"/> -->
|
||||
<module name="IllegalThrows"/>
|
||||
<module name="IllegalType"/>
|
||||
<module name="InnerAssignment"/>
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="NoClone"/>
|
||||
<module name="NoFinalizer"/>
|
||||
<module name="OneStatementPerLine"/>
|
||||
<module name="PackageDeclaration"/>
|
||||
<module name="StringLiteralEquality"/>
|
||||
|
||||
<!-- design -->
|
||||
<module name="OneTopLevelClass"/>
|
||||
<module name="MutableException"/>
|
||||
<module name="InterfaceIsType"/>
|
||||
<module name="ThrowsCount">
|
||||
<property name="max" value="2"/>
|
||||
</module>
|
||||
|
||||
<!-- misc -->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="OuterTypeFilename"/>
|
||||
|
||||
<!-- sizes -->
|
||||
<module name="OuterTypeNumber"/>
|
||||
|
||||
<module name="SuppressWarningsHolder"/>
|
||||
</module>
|
||||
|
||||
<module name="NewlineAtEndOfFile"/>
|
||||
<module name="SuppressWarningsFilter"/>
|
||||
</module>
|
||||
@@ -0,0 +1,8 @@
|
||||
#Import Order
|
||||
0=java
|
||||
1=javax
|
||||
2=org
|
||||
3=com
|
||||
4=
|
||||
5=jadx
|
||||
6=\#
|
||||
@@ -0,0 +1,348 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="16">
|
||||
<profile kind="CodeFormatterProfile" name="jadx eclipse" version="16">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="100"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
@@ -0,0 +1 @@
|
||||
org.gradle.daemon=false
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +6,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# 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
|
||||
|
||||
@@ -90,7 +89,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -114,6 +113,7 @@ fi
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Vendored
+4
-10
@@ -8,14 +8,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem 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=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +46,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows 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.
|
||||
@@ -60,11 +59,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName = 'jadx.cli.JadxCLI'
|
||||
applicationName = 'jadx'
|
||||
|
||||
dependencies {
|
||||
compile(project(':jadx-core'))
|
||||
compile 'com.beust:jcommander:1.74'
|
||||
compile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
}
|
||||
|
||||
applicationDistribution.with {
|
||||
into('') {
|
||||
from '../.'
|
||||
include 'README.md'
|
||||
include 'NOTICE'
|
||||
include 'LICENSE'
|
||||
}
|
||||
}
|
||||
|
||||
startScripts {
|
||||
defaultJvmOpts = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
|
||||
doLast {
|
||||
def str = windowsScript.text
|
||||
str = str.replaceAll('set JAVA_EXE=%JAVA_HOME%/bin/java.exe', 'set JAVA_EXE="%JAVA_HOME%/bin/java.exe"')
|
||||
windowsScript.text = str
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
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 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());
|
||||
addDefaultValue(args, f, opt);
|
||||
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;
|
||||
}
|
||||
|
||||
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
|
||||
Class<?> fieldType = f.getType();
|
||||
if (fieldType == int.class) {
|
||||
try {
|
||||
int val = f.getInt(args);
|
||||
opt.append(" (default: ").append(val).append(')');
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (fieldType == String.class) {
|
||||
try {
|
||||
String val = (String) f.get(args);
|
||||
if (val != null) {
|
||||
opt.append(" (default: ").append(val).append(')');
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addSpaces(StringBuilder str, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
str.append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package jadx.cli;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
|
||||
public class JadxCLI {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
int result = 0;
|
||||
try {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
result = processAndSave(jadxArgs);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
||||
result = 1;
|
||||
} finally {
|
||||
System.exit(result);
|
||||
}
|
||||
}
|
||||
|
||||
static int processAndSave(JadxCLIArgs inputArgs) {
|
||||
JadxArgs args = inputArgs.toJadxArgs();
|
||||
JadxDecompiler jadx = new JadxDecompiler(args);
|
||||
try {
|
||||
jadx.load();
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
return 1;
|
||||
}
|
||||
jadx.save();
|
||||
int errorsCount = jadx.getErrorsCount();
|
||||
if (errorsCount != 0) {
|
||||
jadx.printErrorsReport();
|
||||
LOG.error("finished with errors");
|
||||
} else {
|
||||
LOG.info("done");
|
||||
}
|
||||
return errorsCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
import com.beust.jcommander.Parameter;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxCLIArgs {
|
||||
|
||||
@Parameter(description = "<input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
|
||||
protected List<String> files = new ArrayList<>(1);
|
||||
|
||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||
protected String outDir;
|
||||
|
||||
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
|
||||
protected String outDirSrc;
|
||||
|
||||
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
|
||||
protected String outDirRes;
|
||||
|
||||
@Parameter(names = { "-r", "--no-res" }, description = "do not decode resources")
|
||||
protected boolean skipResources = false;
|
||||
|
||||
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
||||
protected boolean skipSources = false;
|
||||
|
||||
@Parameter(names = { "--single-class" }, description = "decompile a single class")
|
||||
protected String singleClass = null;
|
||||
|
||||
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
||||
protected String outputFormat = "java";
|
||||
|
||||
@Parameter(names = { "-e", "--export-gradle" }, description = "save as android gradle project")
|
||||
protected boolean exportAsGradleProject = false;
|
||||
|
||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||
|
||||
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
|
||||
protected boolean showInconsistentCode = false;
|
||||
|
||||
@Parameter(names = { "--no-imports" }, description = "disable use of imports, always write entire package name")
|
||||
protected boolean useImports = true;
|
||||
|
||||
@Parameter(names = { "--no-debug-info" }, description = "disable debug info")
|
||||
protected boolean debugInfo = true;
|
||||
|
||||
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
|
||||
protected boolean inlineAnonymousClasses = true;
|
||||
|
||||
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
||||
protected boolean replaceConsts = true;
|
||||
|
||||
@Parameter(names = { "--escape-unicode" }, description = "escape non latin characters in strings (with \\u)")
|
||||
protected boolean escapeUnicode = false;
|
||||
|
||||
@Parameter(names = { "--respect-bytecode-access-modifiers" }, description = "don't change original access modifiers")
|
||||
protected boolean respectBytecodeAccessModifiers = false;
|
||||
|
||||
@Parameter(names = { "--deobf" }, description = "activate deobfuscation")
|
||||
protected boolean deobfuscationOn = false;
|
||||
|
||||
@Parameter(names = { "--deobf-min" }, description = "min length of name, renamed if shorter")
|
||||
protected int deobfuscationMinLength = 3;
|
||||
|
||||
@Parameter(names = { "--deobf-max" }, description = "max length of name, renamed if longer")
|
||||
protected int deobfuscationMaxLength = 64;
|
||||
|
||||
@Parameter(names = { "--deobf-rewrite-cfg" }, description = "force to save deobfuscation map")
|
||||
protected boolean deobfuscationForceSave = false;
|
||||
|
||||
@Parameter(names = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = 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")
|
||||
protected boolean verbose = false;
|
||||
|
||||
@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;
|
||||
|
||||
public boolean processArgs(String[] args) {
|
||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
||||
return jcw.parse(args) && process(jcw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set values only for options provided in cmd.
|
||||
* Used to merge saved options and options passed in command line.
|
||||
*/
|
||||
public boolean overrideProvided(String[] args) {
|
||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(newInstance());
|
||||
if (!jcw.parse(args)) {
|
||||
return false;
|
||||
}
|
||||
jcw.overrideProvided(this);
|
||||
return process(jcw);
|
||||
}
|
||||
|
||||
protected JadxCLIArgs newInstance() {
|
||||
return new JadxCLIArgs();
|
||||
}
|
||||
|
||||
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
|
||||
if (printHelp) {
|
||||
jcw.printUsage();
|
||||
return false;
|
||||
}
|
||||
if (printVersion) {
|
||||
System.out.println(JadxDecompiler.getVersion());
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (threadsCount <= 0) {
|
||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
||||
}
|
||||
if (verbose) {
|
||||
ch.qos.logback.classic.Logger rootLogger =
|
||||
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
// remove INFO ThresholdFilter
|
||||
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
|
||||
if (appender != null) {
|
||||
appender.clearAllFilters();
|
||||
}
|
||||
}
|
||||
} catch (JadxException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
jcw.printUsage();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public JadxArgs toJadxArgs() {
|
||||
JadxArgs args = new JadxArgs();
|
||||
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
|
||||
args.setOutDir(FileUtils.toFile(outDir));
|
||||
args.setOutDirSrc(FileUtils.toFile(outDirSrc));
|
||||
args.setOutDirRes(FileUtils.toFile(outDirRes));
|
||||
args.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
||||
args.setThreadsCount(threadsCount);
|
||||
args.setSkipSources(skipSources);
|
||||
if (singleClass != null) {
|
||||
args.setClassFilter(className -> singleClass.equals(className));
|
||||
}
|
||||
args.setSkipResources(skipResources);
|
||||
args.setFallbackMode(fallbackMode);
|
||||
args.setShowInconsistentCode(showInconsistentCode);
|
||||
args.setCfgOutput(cfgOutput);
|
||||
args.setRawCFGOutput(rawCfgOutput);
|
||||
args.setReplaceConsts(replaceConsts);
|
||||
args.setDeobfuscationOn(deobfuscationOn);
|
||||
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||
args.setExportAsGradleProject(exportAsGradleProject);
|
||||
args.setUseImports(useImports);
|
||||
args.setDebugInfo(debugInfo);
|
||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||
args.setRenameCaseSensitive(isRenameCaseSensitive());
|
||||
args.setRenameValid(isRenameValid());
|
||||
args.setRenamePrintable(isRenamePrintable());
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
return args;
|
||||
}
|
||||
|
||||
public List<String> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public String getOutDir() {
|
||||
return outDir;
|
||||
}
|
||||
|
||||
public String getOutDirSrc() {
|
||||
return outDirSrc;
|
||||
}
|
||||
|
||||
public String getOutDirRes() {
|
||||
return outDirRes;
|
||||
}
|
||||
|
||||
public boolean isSkipResources() {
|
||||
return skipResources;
|
||||
}
|
||||
|
||||
public boolean isSkipSources() {
|
||||
return skipSources;
|
||||
}
|
||||
|
||||
public int getThreadsCount() {
|
||||
return threadsCount;
|
||||
}
|
||||
|
||||
public boolean isFallbackMode() {
|
||||
return fallbackMode;
|
||||
}
|
||||
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
|
||||
public boolean isUseImports() {
|
||||
return useImports;
|
||||
}
|
||||
|
||||
public boolean isDebugInfo() {
|
||||
return debugInfo;
|
||||
}
|
||||
|
||||
public boolean isInlineAnonymousClasses() {
|
||||
return inlineAnonymousClasses;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
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) {
|
||||
String values = Arrays.stream(RenameEnum.values())
|
||||
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
|
||||
.collect(Collectors.joining(", "));
|
||||
throw new IllegalArgumentException(
|
||||
'\'' + s + "' is unknown for parameter " + paramName
|
||||
+ ", possible values are " + values);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,67 @@
|
||||
package jadx.cli;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class JadxCLIArgsTest {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLIArgsTest.class);
|
||||
|
||||
@Test
|
||||
public void testInvertedBooleanOption() {
|
||||
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
|
||||
assertThat(parse("").isReplaceConsts(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeUnicodeOption() {
|
||||
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
|
||||
assertThat(parse("").isEscapeUnicode(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSrcOption() {
|
||||
assertThat(parse("--no-src").isSkipSources(), is(true));
|
||||
assertThat(parse("-s").isSkipSources(), is(true));
|
||||
assertThat(parse("").isSkipSources(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionsOverride() {
|
||||
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
|
||||
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
|
||||
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
|
||||
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "--no-imports").isUseImports(), is(false));
|
||||
args.debugInfo = false;
|
||||
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
|
||||
|
||||
args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "").isUseImports(), is(false));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
return parse(new JadxCLIArgs(), args);
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
|
||||
boolean res = jadxArgs.processArgs(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
|
||||
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
|
||||
boolean res = jadxArgs.overrideProvided(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.cli.JadxCLIArgs.RenameConverter;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class RenameConverterTest {
|
||||
|
||||
private RenameConverter converter;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
converter = new RenameConverter("someParam");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void all() {
|
||||
Set<RenameEnum> set = converter.convert("all");
|
||||
assertEquals(3, set.size());
|
||||
assertTrue(set.contains(RenameEnum.CASE));
|
||||
assertTrue(set.contains(RenameEnum.VALID));
|
||||
assertTrue(set.contains(RenameEnum.PRINTABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void none() {
|
||||
Set<RenameEnum> set = converter.convert("none");
|
||||
assertTrue(set.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrong() {
|
||||
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
|
||||
() -> converter.convert("wrong"),
|
||||
"Expected convert() to throw, but it didn't");
|
||||
|
||||
assertEquals("'wrong' is unknown for parameter someParam, "
|
||||
+ "possible values are 'case', 'valid', 'printable'",
|
||||
thrown.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
|
||||
|
||||
dependencies {
|
||||
runtime files(jadxClasspath)
|
||||
|
||||
compile files('lib/dx-1.16.jar')
|
||||
|
||||
compile 'org.ow2.asm:asm:7.1'
|
||||
compile 'org.jetbrains:annotations:17.0.0'
|
||||
compile 'uk.com.robust-it:cloning:1.9.12'
|
||||
compile 'com.google.code.gson:gson:2.8.5'
|
||||
|
||||
compile 'org.smali:baksmali:2.2.7'
|
||||
compile('org.smali:smali:2.2.7') {
|
||||
exclude group: 'com.google.guava'
|
||||
}
|
||||
compile 'com.google.guava:guava:27.1-jre'
|
||||
|
||||
testCompile 'org.apache.commons:commons-lang3:3.8.1'
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,70 @@
|
||||
package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final JavaNode node;
|
||||
private final int line;
|
||||
private final int offset;
|
||||
|
||||
public CodePosition(JavaNode node, int line, int offset) {
|
||||
this.node = node;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public CodePosition(int line, int offset) {
|
||||
this.node = null;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public JavaNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public JavaClass getJavaClass() {
|
||||
JavaClass parent = node.getDeclaringClass();
|
||||
if (parent == null && node instanceof JavaClass) {
|
||||
return (JavaClass) node;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CodePosition that = (CodePosition) o;
|
||||
return line == that.line && offset == that.offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return line + 31 * offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(line);
|
||||
if (offset != 0) {
|
||||
sb.append(':').append(offset);
|
||||
}
|
||||
if (node != null) {
|
||||
sb.append(' ').append(node);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
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;
|
||||
|
||||
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 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 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 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 OutputFormatEnum getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
public boolean isJsonOutput() {
|
||||
return outputFormat == OutputFormatEnum.JSON;
|
||||
}
|
||||
|
||||
public void setOutputFormat(OutputFormatEnum outputFormat) {
|
||||
this.outputFormat = outputFormat;
|
||||
}
|
||||
|
||||
@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
|
||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||
+ ", escapeUnicode=" + escapeUnicode
|
||||
+ ", replaceConsts=" + replaceConsts
|
||||
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
|
||||
+ ", exportAsGradleProject=" + exportAsGradleProject
|
||||
+ ", fsCaseSensitive=" + fsCaseSensitive
|
||||
+ ", renameFlags=" + renameFlags
|
||||
+ ", outputFormat=" + outputFormat
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
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");
|
||||
}
|
||||
if (inputFiles.size() > 1) {
|
||||
for (File inputFile : inputFiles) {
|
||||
String fileName = inputFile.getName();
|
||||
if (fileName.startsWith("--")) {
|
||||
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
||||
}
|
||||
}
|
||||
throw new JadxArgsValidateException("Only one input file supported");
|
||||
}
|
||||
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() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jf.baksmali.Adaptors.ClassDefinition;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.DexFileFactory;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||
import org.jf.util.IndentingWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
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.IDexTreeVisitor;
|
||||
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.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
import jadx.core.xmlgen.ResourcesSaver;
|
||||
|
||||
/**
|
||||
* Jadx API usage example:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* JadxArgs args = new JadxArgs();
|
||||
* args.getInputFiles().add(new File("test.apk"));
|
||||
* args.setOutDir(new File("jadx-test-output"));
|
||||
*
|
||||
* JadxDecompiler jadx = new JadxDecompiler(args);
|
||||
* jadx.load();
|
||||
* jadx.save();
|
||||
* </code>
|
||||
* </pre>
|
||||
* <p>
|
||||
* Instead of 'save()' you can iterate over decompiled classes:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* for(JavaClass cls : jadx.getClasses()) {
|
||||
* System.out.println(cls.getCode());
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
public final class JadxDecompiler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||
|
||||
private JadxArgs args;
|
||||
|
||||
private final List<InputFile> inputFiles = new ArrayList<>();
|
||||
|
||||
private RootNode root;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
|
||||
private List<JavaClass> classes;
|
||||
private List<ResourceFile> resources;
|
||||
|
||||
private BinaryXMLParser xmlParser;
|
||||
|
||||
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new JadxArgs());
|
||||
}
|
||||
|
||||
public JadxDecompiler(JadxArgs args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
reset();
|
||||
JadxArgsValidator.validate(args);
|
||||
init();
|
||||
LOG.info("loading ...");
|
||||
|
||||
loadFiles(args.getInputFiles());
|
||||
|
||||
root = new RootNode(args);
|
||||
root.load(inputFiles);
|
||||
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
|
||||
initVisitors();
|
||||
}
|
||||
|
||||
void init() {
|
||||
this.passes = Jadx.getPassesList(args);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
classes = null;
|
||||
resources = null;
|
||||
xmlParser = null;
|
||||
root = null;
|
||||
passes = null;
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
return Jadx.getVersion();
|
||||
}
|
||||
|
||||
private void loadFiles(List<File> files) {
|
||||
if (files.isEmpty()) {
|
||||
throw new JadxRuntimeException("Empty file list");
|
||||
}
|
||||
inputFiles.clear();
|
||||
for (File file : files) {
|
||||
try {
|
||||
InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Error load file: " + file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void save() {
|
||||
save(!args.isSkipSources(), !args.isSkipResources());
|
||||
}
|
||||
|
||||
public void saveSources() {
|
||||
save(true, false);
|
||||
}
|
||||
|
||||
public void saveResources() {
|
||||
save(false, true);
|
||||
}
|
||||
|
||||
private void save(boolean saveSources, boolean saveResources) {
|
||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||
ex.shutdown();
|
||||
try {
|
||||
ex.awaitTermination(1, TimeUnit.DAYS);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Save interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public ExecutorService getSaveExecutor() {
|
||||
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
|
||||
}
|
||||
|
||||
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
|
||||
if (root == null) {
|
||||
throw new JadxRuntimeException("No loaded files");
|
||||
}
|
||||
int threadsCount = args.getThreadsCount();
|
||||
LOG.debug("processing threads count: {}", threadsCount);
|
||||
|
||||
LOG.info("processing ...");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||
|
||||
File sourcesOutDir;
|
||||
File resOutDir;
|
||||
if (args.isExportAsGradleProject()) {
|
||||
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
|
||||
export.init();
|
||||
sourcesOutDir = export.getSrcOutDir();
|
||||
resOutDir = export.getResOutDir();
|
||||
} else {
|
||||
sourcesOutDir = args.getOutDirSrc();
|
||||
resOutDir = args.getOutDirRes();
|
||||
}
|
||||
if (saveResources) {
|
||||
appendResourcesSave(executor, resOutDir);
|
||||
}
|
||||
if (saveSources) {
|
||||
appendSourcesSave(executor, sourcesOutDir);
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
private void appendResourcesSave(ExecutorService executor, File outDir) {
|
||||
for (ResourceFile resourceFile : getResources()) {
|
||||
executor.execute(new ResourcesSaver(outDir, resourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSourcesSave(ExecutorService executor, File outDir) {
|
||||
final 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 {
|
||||
cls.decompile();
|
||||
SaveCode.save(outDir, cls.getClassNode());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error saving class: {}", cls.getFullName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public List<JavaClass> getClasses() {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (classes == null) {
|
||||
List<ClassNode> classNodeList = root.getClasses(false);
|
||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
if (!classNode.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
}
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
public List<ResourceFile> getResources() {
|
||||
if (resources == null) {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
resources = new ResourcesLoader(this).load(inputFiles);
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
public List<JavaPackage> getPackages() {
|
||||
List<JavaClass> classList = getClasses();
|
||||
if (classList.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<String, List<JavaClass>> map = new HashMap<>();
|
||||
for (JavaClass javaClass : classList) {
|
||||
String pkg = javaClass.getPackage();
|
||||
List<JavaClass> clsList = map.computeIfAbsent(pkg, k -> new ArrayList<>());
|
||||
clsList.add(javaClass);
|
||||
}
|
||||
List<JavaPackage> packages = new ArrayList<>(map.size());
|
||||
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
|
||||
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
Collections.sort(packages);
|
||||
for (JavaPackage pkg : packages) {
|
||||
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
}
|
||||
return Collections.unmodifiableList(packages);
|
||||
}
|
||||
|
||||
public int getErrorsCount() {
|
||||
if (root == null) {
|
||||
return 0;
|
||||
}
|
||||
return root.getErrorsCounter().getErrorCount();
|
||||
}
|
||||
|
||||
public int getWarnsCount() {
|
||||
if (root == null) {
|
||||
return 0;
|
||||
}
|
||||
return root.getErrorsCounter().getWarnsCount();
|
||||
}
|
||||
|
||||
public void printErrorsReport() {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
root.getClsp().printMissingClasses();
|
||||
root.getErrorsCounter().printReport();
|
||||
}
|
||||
|
||||
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, true);
|
||||
}
|
||||
|
||||
void generateSmali(ClassNode cls) {
|
||||
Path path = cls.dex().getDexFile().getPath();
|
||||
String className = Utils.makeQualifiedObjectName(cls.getClassInfo().getType().getObject());
|
||||
try {
|
||||
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), Opcodes.getDefault());
|
||||
boolean decompiled = false;
|
||||
for (DexBackedClassDef classDef : dexFile.getClasses()) {
|
||||
if (classDef.getType().equals(className)) {
|
||||
ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef);
|
||||
StringWriter sw = new StringWriter();
|
||||
classDefinition.writeTo(new IndentingWriter(sw));
|
||||
cls.setSmali(sw.toString());
|
||||
decompiled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!decompiled) {
|
||||
LOG.error("Failed to find smali class {}", className);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error generating smali", e);
|
||||
}
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
List<IDexTreeVisitor> getPasses() {
|
||||
return passes;
|
||||
}
|
||||
|
||||
synchronized BinaryXMLParser getXmlParser() {
|
||||
if (xmlParser == null) {
|
||||
xmlParser = new BinaryXMLParser(root);
|
||||
}
|
||||
return xmlParser;
|
||||
}
|
||||
|
||||
Map<ClassNode, JavaClass> getClassesMap() {
|
||||
return classesMap;
|
||||
}
|
||||
|
||||
Map<MethodNode, JavaMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
JavaMethod getJavaMethodByNode(MethodNode mth) {
|
||||
JavaMethod javaMethod = methodsMap.get(mth);
|
||||
if (javaMethod != null) {
|
||||
return javaMethod;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = classesMap.get(mth.getParentClass());
|
||||
if (javaClass != null) {
|
||||
javaClass.decompile();
|
||||
return methodsMap.get(mth);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<FieldNode, JavaField> getFieldsMap() {
|
||||
return fieldsMap;
|
||||
}
|
||||
|
||||
JavaField getJavaFieldByNode(FieldNode fld) {
|
||||
JavaField javaField = fieldsMap.get(fld);
|
||||
if (javaField != null) {
|
||||
return javaField;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = classesMap.get(fld.getParentClass());
|
||||
if (javaClass != null) {
|
||||
javaClass.decompile();
|
||||
return fieldsMap.get(fld);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public JadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "jadx decompiler " + getVersion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.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;
|
||||
|
||||
public final class JavaClass implements JavaNode {
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
private final ClassNode cls;
|
||||
private final JavaClass parent;
|
||||
|
||||
private List<JavaClass> innerClasses = Collections.emptyList();
|
||||
private List<JavaField> fields = Collections.emptyList();
|
||||
private List<JavaMethod> methods = Collections.emptyList();
|
||||
|
||||
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
|
||||
this.decompiler = decompiler;
|
||||
this.cls = classNode;
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner classes constructor
|
||||
*/
|
||||
JavaClass(ClassNode classNode, JavaClass parent) {
|
||||
this.decompiler = null;
|
||||
this.cls = classNode;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
CodeWriter code = cls.getCode();
|
||||
if (code == null) {
|
||||
decompile();
|
||||
code = cls.getCode();
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return code.getCodeStr();
|
||||
}
|
||||
|
||||
public synchronized void decompile() {
|
||||
if (decompiler == null) {
|
||||
return;
|
||||
}
|
||||
if (cls.getCode() == null) {
|
||||
decompiler.processClass(cls);
|
||||
load();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized String getSmali() {
|
||||
if (decompiler == null) {
|
||||
return null;
|
||||
}
|
||||
if (cls.getSmali() == null) {
|
||||
decompiler.generateSmali(cls);
|
||||
}
|
||||
return cls.getSmali();
|
||||
}
|
||||
|
||||
public synchronized void unload() {
|
||||
cls.unload();
|
||||
}
|
||||
|
||||
public ClassNode getClassNode() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
private void load() {
|
||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||
int inClsCount = cls.getInnerClasses().size();
|
||||
if (inClsCount != 0) {
|
||||
List<JavaClass> list = new ArrayList<>(inClsCount);
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(inner, this);
|
||||
javaClass.load();
|
||||
list.add(javaClass);
|
||||
rootDecompiler.getClassesMap().put(inner, javaClass);
|
||||
}
|
||||
}
|
||||
this.innerClasses = Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
int fieldsCount = cls.getFields().size();
|
||||
if (fieldsCount != 0) {
|
||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaField javaField = new JavaField(f, this);
|
||||
flds.add(javaField);
|
||||
rootDecompiler.getFieldsMap().put(f, javaField);
|
||||
}
|
||||
}
|
||||
this.fields = Collections.unmodifiableList(flds);
|
||||
}
|
||||
|
||||
int methodsCount = cls.getMethods().size();
|
||||
if (methodsCount != 0) {
|
||||
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
||||
mths.add(javaMethod);
|
||||
rootDecompiler.getMethodsMap().put(m, javaMethod);
|
||||
}
|
||||
}
|
||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||
this.methods = Collections.unmodifiableList(mths);
|
||||
}
|
||||
}
|
||||
|
||||
private JadxDecompiler getRootDecompiler() {
|
||||
if (parent != null) {
|
||||
return parent.getRootDecompiler();
|
||||
}
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
decompile();
|
||||
CodeWriter code = cls.getCode();
|
||||
if (code == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return code.getAnnotations();
|
||||
}
|
||||
|
||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
|
||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||
CodePosition codePosition = entry.getKey();
|
||||
Object obj = entry.getValue();
|
||||
if (obj instanceof LineAttrNode) {
|
||||
JavaNode node = convertNode(obj);
|
||||
if (node != null) {
|
||||
resultMap.put(codePosition, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaNode convertNode(Object obj) {
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
}
|
||||
if (obj instanceof ClassNode) {
|
||||
return getRootDecompiler().getClassesMap().get(obj);
|
||||
}
|
||||
if (obj instanceof MethodNode) {
|
||||
return getRootDecompiler().getJavaMethodByNode(((MethodNode) obj));
|
||||
}
|
||||
if (obj instanceof FieldNode) {
|
||||
return getRootDecompiler().getJavaFieldByNode((FieldNode) obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
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 Integer getSourceLine(int decompiledLine) {
|
||||
decompile();
|
||||
return cls.getCode().getLineMapping().get(decompiledLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return cls.getShortName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return cls.getFullName();
|
||||
}
|
||||
|
||||
public String getPackage() {
|
||||
return cls.getPackage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent == null ? this : parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessInfo() {
|
||||
return cls.getAccessFlags();
|
||||
}
|
||||
|
||||
public List<JavaClass> getInnerClasses() {
|
||||
decompile();
|
||||
return innerClasses;
|
||||
}
|
||||
|
||||
public List<JavaField> getFields() {
|
||||
decompile();
|
||||
return fields;
|
||||
}
|
||||
|
||||
public List<JavaMethod> getMethods() {
|
||||
decompile();
|
||||
return methods;
|
||||
}
|
||||
|
||||
public int getDecompiledLine() {
|
||||
return cls.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return cls.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFullName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
|
||||
public final class JavaField implements JavaNode {
|
||||
|
||||
private final FieldNode field;
|
||||
private final JavaClass parent;
|
||||
|
||||
JavaField(FieldNode f, JavaClass cls) {
|
||||
this.field = f;
|
||||
this.parent = cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return field.getAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return parent.getFullName() + '.' + getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return field.getAccessFlags();
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
|
||||
}
|
||||
|
||||
public int getDecompiledLine() {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public final class JavaMethod implements JavaNode {
|
||||
private final MethodNode mth;
|
||||
private final JavaClass parent;
|
||||
|
||||
JavaMethod(JavaClass cls, MethodNode m) {
|
||||
this.parent = cls;
|
||||
this.mth = m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return mth.getAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return mth.getMethodInfo().getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return mth.getAccessFlags();
|
||||
}
|
||||
|
||||
public List<ArgType> getArguments() {
|
||||
if (mth.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<RegisterArg> arguments = mth.getArguments(false);
|
||||
Stream<ArgType> argTypeStream;
|
||||
if (arguments == null || arguments.isEmpty() || mth.isNoCode()) {
|
||||
argTypeStream = mth.getMethodInfo().getArgumentsTypes().stream();
|
||||
} else {
|
||||
argTypeStream = arguments.stream().map(RegisterArg::getType);
|
||||
}
|
||||
return argTypeStream
|
||||
.map(type -> ArgType.tryToResolveClassAlias(mth.dex(), type))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
ArgType retType = mth.getReturnType();
|
||||
if (retType == null) {
|
||||
retType = mth.getMethodInfo().getReturnType();
|
||||
}
|
||||
return ArgType.tryToResolveClassAlias(mth.dex(), retType);
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
return mth.getMethodInfo().isConstructor();
|
||||
}
|
||||
|
||||
public boolean isClassInit() {
|
||||
return mth.getMethodInfo().isClassInit();
|
||||
}
|
||||
|
||||
public int getDecompiledLine() {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package jadx.api;
|
||||
|
||||
public interface JavaNode {
|
||||
|
||||
String getName();
|
||||
|
||||
String getFullName();
|
||||
|
||||
JavaClass getDeclaringClass();
|
||||
|
||||
JavaClass getTopParentClass();
|
||||
|
||||
int getDecompiledLine();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
private final String name;
|
||||
private final List<JavaClass> classes;
|
||||
|
||||
JavaPackage(String name, List<JavaClass> classes) {
|
||||
this.name = name;
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
// TODO: store full package name
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<JavaClass> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JavaPackage that = (JavaPackage) o;
|
||||
return name.equals(that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ResourceFile {
|
||||
|
||||
public static final class ZipRef {
|
||||
private final File zipFile;
|
||||
private final String entryName;
|
||||
|
||||
public ZipRef(File zipFile, String entryName) {
|
||||
this.zipFile = zipFile;
|
||||
this.entryName = entryName;
|
||||
}
|
||||
|
||||
public File getZipFile() {
|
||||
return zipFile;
|
||||
}
|
||||
|
||||
public String getEntryName() {
|
||||
return entryName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ZipRef{" + zipFile + ", '" + entryName + "'}";
|
||||
}
|
||||
}
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
private final String name;
|
||||
private final ResourceType type;
|
||||
private ZipRef zipRef;
|
||||
|
||||
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
if (!ZipSecurity.isValidZipEntryName(name)) {
|
||||
return null;
|
||||
}
|
||||
return new ResourceFile(decompiler, name, type);
|
||||
}
|
||||
|
||||
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
this.decompiler = decompiler;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ResourceType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public ResContainer loadContent() {
|
||||
return ResourcesLoader.loadContent(decompiler, this);
|
||||
}
|
||||
|
||||
void setZipRef(ZipRef zipRef) {
|
||||
this.zipRef = zipRef;
|
||||
}
|
||||
|
||||
public ZipRef getZipRef() {
|
||||
return zipRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResourceFile{name='" + name + '\'' + ", type=" + type + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ResourceFileContent extends ResourceFile {
|
||||
private final CodeWriter content;
|
||||
|
||||
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
|
||||
super(null, name, type);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResContainer loadContent() {
|
||||
return ResContainer.textResource(getName(), content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jadx.api;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(".dex", ".jar", ".class"),
|
||||
MANIFEST("AndroidManifest.xml"),
|
||||
XML(".xml"),
|
||||
ARSC(".arsc"),
|
||||
FONT(".ttf"),
|
||||
IMG(".png", ".gif", ".jpg"),
|
||||
LIB(".so"),
|
||||
UNKNOWN;
|
||||
|
||||
private final String[] exts;
|
||||
|
||||
ResourceType(String... exts) {
|
||||
this.exts = exts;
|
||||
}
|
||||
|
||||
public String[] getExts() {
|
||||
return exts;
|
||||
}
|
||||
|
||||
public static ResourceType getFileType(String fileName) {
|
||||
for (ResourceType type : ResourceType.values()) {
|
||||
for (String ext : type.getExts()) {
|
||||
if (fileName.endsWith(ext)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
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.InputFile;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
|
||||
import static jadx.core.utils.files.FileUtils.copyStream;
|
||||
|
||||
// TODO: move to core package
|
||||
public final class ResourcesLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||
|
||||
private final JadxDecompiler jadxRef;
|
||||
|
||||
ResourcesLoader(JadxDecompiler jadxRef) {
|
||||
this.jadxRef = jadxRef;
|
||||
}
|
||||
|
||||
List<ResourceFile> load(List<InputFile> inputFiles) {
|
||||
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
||||
for (InputFile file : inputFiles) {
|
||||
loadFile(list, file.getFile());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public interface ResourceDecoder<T> {
|
||||
T decode(long size, InputStream is) throws IOException;
|
||||
}
|
||||
|
||||
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
|
||||
try {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
File file = new File(rf.getName());
|
||||
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
return decoder.decode(file.length(), inputStream);
|
||||
}
|
||||
} else {
|
||||
try (ZipFile zipFile = new ZipFile(zipRef.getZipFile())) {
|
||||
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
||||
if (entry == null) {
|
||||
throw new IOException("Zip entry not found: " + zipRef);
|
||||
}
|
||||
if (!ZipSecurity.isValidZipEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
|
||||
return decoder.decode(entry.getSize(), inputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JadxException("Error decode: " + rf.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
|
||||
try {
|
||||
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
|
||||
} catch (JadxException e) {
|
||||
LOG.error("Decode error", e);
|
||||
CodeWriter cw = new CodeWriter();
|
||||
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
||||
Utils.appendStackTrace(cw, e.getCause());
|
||||
return ResContainer.textResource(rf.getName(), cw);
|
||||
}
|
||||
}
|
||||
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream) throws IOException {
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
|
||||
return ResContainer.textResource(rf.getName(), content);
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
|
||||
|
||||
case IMG:
|
||||
return decodeImage(rf, inputStream);
|
||||
|
||||
default:
|
||||
return ResContainer.resourceFileLink(rf);
|
||||
}
|
||||
}
|
||||
|
||||
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
|
||||
String name = rf.getName();
|
||||
if (name.endsWith(".9.png")) {
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
try {
|
||||
decoder.decode(inputStream, os);
|
||||
return ResContainer.decodedData(rf.getName(), os.toByteArray());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||
}
|
||||
}
|
||||
return ResContainer.resourceFileLink(rf);
|
||||
}
|
||||
|
||||
private void loadFile(List<ResourceFile> list, File file) {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
try (ZipFile zip = new ZipFile(file)) {
|
||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
if (ZipSecurity.isValidZipEntry(entry)) {
|
||||
addEntry(list, file, entry);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
|
||||
addResourceFile(list, file);
|
||||
}
|
||||
}
|
||||
|
||||
private void addResourceFile(List<ResourceFile> list, File file) {
|
||||
String name = file.getAbsolutePath();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
list.add(rf);
|
||||
}
|
||||
}
|
||||
|
||||
private void addEntry(List<ResourceFile> list, File zipFile, ZipEntry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
String name = entry.getName();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
rf.setZipRef(new ZipRef(zipFile, name));
|
||||
list.add(rf);
|
||||
}
|
||||
}
|
||||
|
||||
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||
copyStream(is, baos);
|
||||
cw.add(baos.toString("UTF-8"));
|
||||
return cw;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
package jadx;
|
||||
|
||||
import jadx.utils.Utils;
|
||||
package jadx.core;
|
||||
|
||||
public class Consts {
|
||||
public static final String JADX_VERSION = Utils.getJadxVersion();
|
||||
|
||||
public static final boolean DEBUG = false;
|
||||
|
||||
public static final String CLASS_OBJECT = "java.lang.Object";
|
||||
@@ -22,4 +18,10 @@ public class Consts {
|
||||
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
|
||||
|
||||
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
|
||||
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
||||
|
||||
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
|
||||
|
||||
private Consts() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package jadx.core;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
import jadx.core.dex.visitors.ConstructorVisitor;
|
||||
import jadx.core.dex.visitors.DependencyCollector;
|
||||
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.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
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.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.DebugInfoParseVisitor;
|
||||
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;
|
||||
|
||||
public class Jadx {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||
|
||||
private Jadx() {
|
||||
}
|
||||
|
||||
static {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("debug enabled");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
if (args.isFallbackMode()) {
|
||||
passes.add(new FallbackModeVisitor());
|
||||
} else {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoParseVisitor());
|
||||
}
|
||||
|
||||
passes.add(new BlockSplitter());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinish());
|
||||
|
||||
passes.add(new SSATransform());
|
||||
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 ModVisitor());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new ReSugarCode());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dump());
|
||||
}
|
||||
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CleanRegions());
|
||||
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
|
||||
passes.add(new DependencyCollector());
|
||||
passes.add(new RenameVisitor());
|
||||
}
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
try {
|
||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
try (InputStream is = resources.nextElement().openStream()) {
|
||||
Manifest manifest = new Manifest(is);
|
||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||
if (ver != null) {
|
||||
return ver;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Can't get manifest file", e);
|
||||
}
|
||||
return "dev";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package jadx.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
|
||||
import static jadx.core.dex.nodes.ProcessState.STARTED;
|
||||
|
||||
public final class ProcessClass {
|
||||
|
||||
private ProcessClass() {
|
||||
}
|
||||
|
||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
|
||||
if (!generateCode && cls.getState() == PROCESSED) {
|
||||
return;
|
||||
}
|
||||
synchronized (getSyncObj(cls)) {
|
||||
try {
|
||||
if (cls.getState() == NOT_LOADED) {
|
||||
cls.load();
|
||||
cls.setState(STARTED);
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
DepthTraversal.visit(visitor, cls);
|
||||
}
|
||||
cls.setState(PROCESSED);
|
||||
}
|
||||
if (cls.getState() == PROCESSED && generateCode) {
|
||||
processDependencies(cls, passes);
|
||||
CodeGen.generate(cls);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getSyncObj(ClassNode cls) {
|
||||
return cls.getClassInfo();
|
||||
}
|
||||
|
||||
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
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 jadx.core.utils.files.ZipSecurity;
|
||||
|
||||
/**
|
||||
* Classes list for import into classpath graph
|
||||
*/
|
||||
public class ClsSet {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClsSet.class);
|
||||
|
||||
private static final String CLST_EXTENSION = ".jcst";
|
||||
private static final String CLST_FILENAME = "core" + CLST_EXTENSION;
|
||||
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
|
||||
|
||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||
private static final int VERSION = 2;
|
||||
|
||||
private static final String STRING_CHARSET = "US-ASCII";
|
||||
|
||||
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
|
||||
|
||||
private enum TypeEnum {
|
||||
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
|
||||
}
|
||||
|
||||
private NClass[] classes;
|
||||
|
||||
public void load(RootNode root) {
|
||||
List<ClassNode> list = root.getClasses(true);
|
||||
Map<String, NClass> names = new HashMap<>(list.size());
|
||||
int k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
String clsRawName = cls.getRawName();
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
cls.load();
|
||||
NClass nClass = new NClass(clsRawName, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
nClass.setMethods(loadMethods(cls, nClass));
|
||||
} else {
|
||||
names.put(clsRawName, null);
|
||||
}
|
||||
}
|
||||
classes = new NClass[k];
|
||||
k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
NClass nClass = getCls(cls.getRawName(), names);
|
||||
if (nClass == null) {
|
||||
throw new JadxRuntimeException("Missing class: " + cls);
|
||||
}
|
||||
nClass.setParents(makeParentsArray(cls, names));
|
||||
classes[k] = nClass;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NMethod[] loadMethods(ClassNode cls, NClass nClass) {
|
||||
List<NMethod> methods = new ArrayList<>();
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.getAccessFlags().isPublic()
|
||||
&& !m.getAccessFlags().isProtected()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ArgType> args = new ArrayList<>();
|
||||
|
||||
boolean genericArg = false;
|
||||
for (RegisterArg r : m.getArguments(false)) {
|
||||
ArgType argType = r.getType();
|
||||
if (argType.isGeneric() || argType.isGenericType()) {
|
||||
args.add(argType);
|
||||
genericArg = true;
|
||||
} else {
|
||||
args.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
ArgType retType = m.getReturnType();
|
||||
if (!retType.isGeneric() && !retType.isGenericType()) {
|
||||
retType = null;
|
||||
}
|
||||
|
||||
boolean varArgs = m.getAccessFlags().isVarArgs();
|
||||
|
||||
if (genericArg || retType != null || varArgs) {
|
||||
methods.add(new NMethod(
|
||||
m.getMethodInfo().getShortId(),
|
||||
args.isEmpty()
|
||||
? new ArgType[0]
|
||||
: args.toArray(new ArgType[args.size()]),
|
||||
retType,
|
||||
varArgs));
|
||||
}
|
||||
}
|
||||
return methods.toArray(new NMethod[methods.size()]);
|
||||
}
|
||||
|
||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
NClass c = getCls(superClass.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
}
|
||||
for (ArgType iface : cls.getInterfaces()) {
|
||||
NClass c = getCls(iface.getObject(), names);
|
||||
if (c != null) {
|
||||
parents.add(c);
|
||||
}
|
||||
}
|
||||
int size = parents.size();
|
||||
if (size == 0) {
|
||||
return EMPTY_NCLASS_ARRAY;
|
||||
}
|
||||
return parents.toArray(new NClass[size]);
|
||||
}
|
||||
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
NClass cls = names.get(fullName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Class not found: {}", fullName);
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
void save(Path path) throws IOException {
|
||||
FileUtils.makeDirsForFile(path);
|
||||
String outputName = path.getFileName().toString();
|
||||
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||
try (BufferedOutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(path))) {
|
||||
save(outputStream);
|
||||
}
|
||||
} else if (outputName.endsWith(".jar")) {
|
||||
Path temp = FileUtils.createTempFile(".zip");
|
||||
Files.copy(path, temp, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
|
||||
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
|
||||
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
|
||||
out.putNextEntry(new ZipEntry(clst));
|
||||
save(out);
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (!entry.getName().equals(clst)) {
|
||||
out.putNextEntry(new ZipEntry(entry.getName()));
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
}
|
||||
}
|
||||
|
||||
public void save(OutputStream output) throws IOException {
|
||||
DataOutputStream out = new DataOutputStream(output);
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
LOG.info("Classes count: {}", classes.length);
|
||||
Map<String, NClass> names = new HashMap<>(classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
names.put(cls.getName(), cls);
|
||||
}
|
||||
for (NClass cls : classes) {
|
||||
NClass[] parents = cls.getParents();
|
||||
out.writeByte(parents.length);
|
||||
for (NClass parent : parents) {
|
||||
out.writeInt(parent.getId());
|
||||
}
|
||||
NMethod[] methods = cls.getMethods();
|
||||
out.writeByte(methods.length);
|
||||
for (NMethod method : methods) {
|
||||
writeMethod(out, method, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
|
||||
int argCount = 0;
|
||||
ArgType[] argTypes = method.getArgType();
|
||||
for (ArgType arg : argTypes) {
|
||||
if (arg != null) {
|
||||
argCount++;
|
||||
}
|
||||
}
|
||||
|
||||
writeLongString(out, method.getShortId());
|
||||
out.writeByte(argCount);
|
||||
|
||||
// last argument first
|
||||
for (int i = argTypes.length - 1; i >= 0; i--) {
|
||||
ArgType argType = argTypes[i];
|
||||
if (argType != null) {
|
||||
out.writeByte(i);
|
||||
writeArgType(out, argType, names);
|
||||
}
|
||||
}
|
||||
|
||||
if (method.getReturnType() != null) {
|
||||
out.writeBoolean(true);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
} else {
|
||||
out.writeBoolean(false);
|
||||
}
|
||||
|
||||
out.writeBoolean(method.isVarArgs());
|
||||
}
|
||||
|
||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
|
||||
if (argType.getWildcardType() != null) {
|
||||
out.writeByte(TypeEnum.WILDCARD.ordinal());
|
||||
int bounds = argType.getWildcardBounds();
|
||||
out.writeByte(bounds);
|
||||
if (bounds != 0) {
|
||||
writeArgType(out, argType.getWildcardType(), names);
|
||||
}
|
||||
} else if (argType.isGeneric()) {
|
||||
out.writeByte(TypeEnum.GENERIC.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
ArgType[] types = argType.getGenericTypes();
|
||||
if (types == null) {
|
||||
out.writeByte(0);
|
||||
} else {
|
||||
out.writeByte(types.length);
|
||||
for (ArgType type : types) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
} else if (argType.isGenericType()) {
|
||||
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal());
|
||||
writeString(out, argType.getObject());
|
||||
} else if (argType.isObject()) {
|
||||
out.writeByte(TypeEnum.OBJECT.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
} else if (argType.isArray()) {
|
||||
out.writeByte(TypeEnum.ARRAY.ordinal());
|
||||
writeArgType(out, argType.getArrayElement(), names);
|
||||
} else if (argType.isPrimitive()) {
|
||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Cannot save type: " + argType);
|
||||
}
|
||||
}
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
|
||||
if (input == null) {
|
||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
||||
}
|
||||
load(input);
|
||||
}
|
||||
}
|
||||
|
||||
public void load(File input) throws IOException, DecodeException {
|
||||
String name = input.getName();
|
||||
try (InputStream inputStream = new FileInputStream(input)) {
|
||||
if (name.endsWith(CLST_EXTENSION)) {
|
||||
load(inputStream);
|
||||
} else if (name.endsWith(".jar")) {
|
||||
try (ZipInputStream in = new ZipInputStream(inputStream)) {
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) {
|
||||
load(in);
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void load(InputStream input) throws IOException, DecodeException {
|
||||
try (DataInputStream in = new DataInputStream(input)) {
|
||||
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||
int readHeaderLength = in.read(header);
|
||||
int version = in.readByte();
|
||||
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|
||||
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|
||||
|| version != VERSION) {
|
||||
throw new DecodeException("Wrong jadx class set header");
|
||||
}
|
||||
int count = in.readInt();
|
||||
classes = new NClass[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
String name = readString(in);
|
||||
classes[i] = new NClass(name, i);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
int pCount = in.readByte();
|
||||
NClass[] parents = new NClass[pCount];
|
||||
for (int j = 0; j < pCount; j++) {
|
||||
parents[j] = classes[in.readInt()];
|
||||
}
|
||||
classes[i].setParents(parents);
|
||||
|
||||
int mCount = in.readByte();
|
||||
NMethod[] methods = new NMethod[mCount];
|
||||
for (int j = 0; j < mCount; j++) {
|
||||
methods[j] = readMethod(in);
|
||||
}
|
||||
classes[i].setMethods(methods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NMethod readMethod(DataInputStream in) throws IOException {
|
||||
String shortId = readLongString(in);
|
||||
int argCount = in.readByte();
|
||||
ArgType[] argTypes = null;
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
int index = in.readByte();
|
||||
ArgType argType = readArgType(in);
|
||||
if (argTypes == null) {
|
||||
argTypes = new ArgType[index + 1];
|
||||
}
|
||||
argTypes[index] = argType;
|
||||
}
|
||||
ArgType retType = in.readBoolean() ? readArgType(in) : null;
|
||||
boolean varArgs = in.readBoolean();
|
||||
return new NMethod(shortId, argTypes, retType, varArgs);
|
||||
}
|
||||
|
||||
private ArgType readArgType(DataInputStream in) throws IOException {
|
||||
int ordinal = in.readByte();
|
||||
switch (TypeEnum.values()[ordinal]) {
|
||||
case WILDCARD:
|
||||
int bounds = in.readByte();
|
||||
return bounds == 0
|
||||
? ArgType.wildcard()
|
||||
: ArgType.wildcard(readArgType(in), bounds);
|
||||
case GENERIC:
|
||||
String obj = classes[in.readInt()].getName();
|
||||
int typeLength = in.readByte();
|
||||
ArgType[] generics;
|
||||
if (typeLength == 0) {
|
||||
generics = null;
|
||||
} else {
|
||||
generics = new ArgType[typeLength];
|
||||
for (int i = 0; i < typeLength; i++) {
|
||||
generics[i] = readArgType(in);
|
||||
}
|
||||
}
|
||||
return ArgType.generic(obj, generics);
|
||||
case GENERIC_TYPE:
|
||||
return ArgType.genericType(readString(in));
|
||||
case OBJECT:
|
||||
return ArgType.object(classes[in.readInt()].getName());
|
||||
case ARRAY:
|
||||
return ArgType.array(readArgType(in));
|
||||
case PRIMITIVE:
|
||||
int shortName = in.readByte();
|
||||
switch (shortName) {
|
||||
case 'Z':
|
||||
return ArgType.BOOLEAN;
|
||||
case 'C':
|
||||
return ArgType.CHAR;
|
||||
case 'B':
|
||||
return ArgType.BYTE;
|
||||
case 'S':
|
||||
return ArgType.SHORT;
|
||||
case 'I':
|
||||
return ArgType.INT;
|
||||
case 'F':
|
||||
return ArgType.FLOAT;
|
||||
case 'J':
|
||||
return ArgType.LONG;
|
||||
case 'D':
|
||||
return ArgType.DOUBLE;
|
||||
default:
|
||||
return ArgType.VOID;
|
||||
}
|
||||
default:
|
||||
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeByte(bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static void writeLongString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeShort(bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static String readString(DataInputStream in) throws IOException {
|
||||
int len = in.readByte();
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
private static String readLongString(DataInputStream in) throws IOException {
|
||||
int len = in.readShort();
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
private static String readString(DataInputStream in, int len) throws IOException {
|
||||
byte[] bytes = new byte[len];
|
||||
int count = in.read(bytes);
|
||||
while (count != len) {
|
||||
int res = in.read(bytes, count, len - count);
|
||||
if (res == -1) {
|
||||
throw new IOException("String read error");
|
||||
} else {
|
||||
count += res;
|
||||
}
|
||||
}
|
||||
return new String(bytes, STRING_CHARSET);
|
||||
}
|
||||
|
||||
public int getClassesCount() {
|
||||
return classes.length;
|
||||
}
|
||||
|
||||
public void addToMap(Map<String, NClass> nameMap) {
|
||||
for (NClass cls : classes) {
|
||||
nameMap.put(cls.getName(), cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
* Classes hierarchy graph
|
||||
*/
|
||||
public class ClspGraph {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private Map<String, NClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<>();
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
ClsSet set = new ClsSet();
|
||||
set.load();
|
||||
addClasspath(set);
|
||||
}
|
||||
|
||||
public void addClasspath(ClsSet set) {
|
||||
if (nameMap == null) {
|
||||
nameMap = new HashMap<>(set.getClassesCount());
|
||||
set.addToMap(nameMap);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Classpath already loaded");
|
||||
}
|
||||
}
|
||||
|
||||
public void addApp(List<ClassNode> classes) {
|
||||
if (nameMap == null) {
|
||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||
}
|
||||
int size = classes.size();
|
||||
NClass[] nClasses = new NClass[size];
|
||||
int k = 0;
|
||||
for (ClassNode cls : classes) {
|
||||
nClasses[k++] = addClass(cls);
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClsKnown(String fullName) {
|
||||
return nameMap.containsKey(fullName);
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
String rawName = cls.getRawName();
|
||||
NClass nClass = new NClass(rawName, -1);
|
||||
nameMap.put(rawName, nClass);
|
||||
return nClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code clsName} instanceof {@code implClsName}
|
||||
*/
|
||||
public boolean isImplements(String clsName, String implClsName) {
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
return anc.contains(implClsName);
|
||||
}
|
||||
|
||||
public List<String> getImplementations(String clsName) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String cls : nameMap.keySet()) {
|
||||
if (isImplements(cls, clsName)) {
|
||||
list.add(cls);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public String getCommonAncestor(String clsName, String implClsName) {
|
||||
if (clsName.equals(implClsName)) {
|
||||
return clsName;
|
||||
}
|
||||
NClass cls = nameMap.get(implClsName);
|
||||
if (cls == null) {
|
||||
missingClasses.add(clsName);
|
||||
return null;
|
||||
}
|
||||
if (isImplements(clsName, implClsName)) {
|
||||
return implClsName;
|
||||
}
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
return searchCommonParent(anc, cls);
|
||||
}
|
||||
|
||||
private String searchCommonParent(Set<String> anc, NClass cls) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
String name = p.getName();
|
||||
if (anc.contains(name)) {
|
||||
return name;
|
||||
}
|
||||
String r = searchCommonParent(anc, p);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<String> getAncestors(String clsName) {
|
||||
Set<String> result = ancestorCache.get(clsName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
NClass cls = nameMap.get(clsName);
|
||||
if (cls == null) {
|
||||
missingClasses.add(clsName);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
result = new HashSet<>();
|
||||
addAncestorsNames(cls, result);
|
||||
if (result.isEmpty()) {
|
||||
result = Collections.emptySet();
|
||||
}
|
||||
ancestorCache.put(clsName, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addAncestorsNames(NClass cls, Set<String> result) {
|
||||
boolean isNew = result.add(cls.getName());
|
||||
if (isNew) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
addAncestorsNames(p, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,75 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
Path output = Paths.get(args[0]);
|
||||
|
||||
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
File f = new File(args[i]);
|
||||
if (f.isDirectory()) {
|
||||
addFilesFromDirectory(f, inputFiles);
|
||||
} else {
|
||||
InputFile.addFilesFrom(f, inputFiles, false);
|
||||
}
|
||||
}
|
||||
for (InputFile inputFile : inputFiles) {
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode(new JadxArgs());
|
||||
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) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
addFilesFromDirectory(file, inputFiles);
|
||||
} else {
|
||||
try {
|
||||
InputFile.addFilesFrom(file, inputFiles, false);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
/**
|
||||
* Class node in classpath graph
|
||||
*/
|
||||
public class NClass {
|
||||
|
||||
private final String name;
|
||||
private NClass[] parents;
|
||||
private NMethod[] methods;
|
||||
private final 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 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;
|
||||
}
|
||||
|
||||
public void setMethods(NMethod[] methods) {
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public NMethod[] getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
/**
|
||||
* Generic method node in classpath graph.
|
||||
*/
|
||||
public class NMethod {
|
||||
|
||||
private final String shortId;
|
||||
private final ArgType[] argType;
|
||||
private final ArgType retType;
|
||||
private final boolean varArgs;
|
||||
|
||||
public NMethod(String shortId, ArgType[] argType, ArgType retType, boolean varArgs) {
|
||||
this.shortId = shortId;
|
||||
this.argType = argType;
|
||||
this.retType = retType;
|
||||
this.varArgs = varArgs;
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
public ArgType[] getArgType() {
|
||||
return argType;
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
return retType;
|
||||
}
|
||||
|
||||
public boolean isVarArgs() {
|
||||
return varArgs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
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.core.Consts;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
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.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class AnnotationGen {
|
||||
|
||||
private final ClassNode cls;
|
||||
private final ClassGen classGen;
|
||||
|
||||
public AnnotationGen(ClassNode cls, ClassGen classGen) {
|
||||
this.cls = cls;
|
||||
this.classGen = classGen;
|
||||
}
|
||||
|
||||
public void addForClass(CodeWriter code) {
|
||||
add(cls, code);
|
||||
}
|
||||
|
||||
public void addForMethod(CodeWriter code, MethodNode mth) {
|
||||
add(mth, code);
|
||||
}
|
||||
|
||||
public void addForField(CodeWriter code, FieldNode field) {
|
||||
add(field, code);
|
||||
}
|
||||
|
||||
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
|
||||
List<AnnotationsList> paramList = paramsAnnotations.getParamList();
|
||||
if (n >= paramList.size()) {
|
||||
return;
|
||||
}
|
||||
AnnotationsList aList = paramList.get(n);
|
||||
if (aList == null || aList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
formatAnnotation(code, a);
|
||||
code.add(' ');
|
||||
}
|
||||
}
|
||||
|
||||
private void add(IAttributeNode node, CodeWriter code) {
|
||||
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
|
||||
if (aList == null || aList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
String aCls = a.getAnnotationClass();
|
||||
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
||||
code.startLine();
|
||||
formatAnnotation(code, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void formatAnnotation(CodeWriter code, Annotation a) {
|
||||
code.add('@');
|
||||
ClassNode annCls = cls.dex().resolveClass(a.getType());
|
||||
if (annCls != null) {
|
||||
classGen.useClass(code, annCls);
|
||||
} else {
|
||||
classGen.useType(code, a.getType());
|
||||
}
|
||||
|
||||
Map<String, Object> vl = a.getValues();
|
||||
if (!vl.isEmpty()) {
|
||||
code.add('(');
|
||||
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
|
||||
Entry<String, Object> e = it.next();
|
||||
String paramName = getParamName(annCls, e.getKey());
|
||||
if (paramName.equals("value") && vl.size() == 1) {
|
||||
// don't add "value = " if no other parameters
|
||||
} else {
|
||||
code.add(paramName);
|
||||
code.add(" = ");
|
||||
}
|
||||
encodeValue(code, e.getValue());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addThrows(MethodNode mth, CodeWriter code) {
|
||||
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
|
||||
if (an != null) {
|
||||
Object exs = an.getDefaultValue();
|
||||
code.add(" throws ");
|
||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
|
||||
ArgType ex = it.next();
|
||||
classGen.useType(code, ex);
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Object getAnnotationDefaultValue(String name) {
|
||||
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
|
||||
if (an != null) {
|
||||
Annotation defAnnotation = (Annotation) an.getDefaultValue();
|
||||
return defAnnotation.getValues().get(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: refactor this boilerplate code
|
||||
public void encodeValue(CodeWriter code, Object val) {
|
||||
if (val == null) {
|
||||
code.add("null");
|
||||
return;
|
||||
}
|
||||
if (val instanceof String) {
|
||||
code.add(getStringUtils().unescapeString((String) val));
|
||||
} else if (val instanceof Integer) {
|
||||
code.add(TypeGen.formatInteger((Integer) val));
|
||||
} else if (val instanceof Character) {
|
||||
code.add(getStringUtils().unescapeChar((Character) val));
|
||||
} else if (val instanceof Boolean) {
|
||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
||||
} else if (val instanceof Float) {
|
||||
code.add(TypeGen.formatFloat((Float) val));
|
||||
} else if (val instanceof Double) {
|
||||
code.add(TypeGen.formatDouble((Double) val));
|
||||
} else if (val instanceof Long) {
|
||||
code.add(TypeGen.formatLong((Long) val));
|
||||
} else if (val instanceof Short) {
|
||||
code.add(TypeGen.formatShort((Short) val));
|
||||
} else if (val instanceof Byte) {
|
||||
code.add(TypeGen.formatByte((Byte) val));
|
||||
} else if (val instanceof ArgType) {
|
||||
classGen.useType(code, (ArgType) val);
|
||||
code.add(".class");
|
||||
} else if (val instanceof FieldInfo) {
|
||||
// must be a static field
|
||||
FieldInfo field = (FieldInfo) val;
|
||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||
} else if (val instanceof Iterable) {
|
||||
code.add('{');
|
||||
Iterator<?> it = ((Iterable<?>) val).iterator();
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
encodeValue(code, obj);
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add('}');
|
||||
} else if (val instanceof Annotation) {
|
||||
formatAnnotation(code, (Annotation) val);
|
||||
} else {
|
||||
// TODO: also can be method values
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')');
|
||||
}
|
||||
}
|
||||
|
||||
private StringUtils getStringUtils() {
|
||||
return cls.dex().root().getStringUtils();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,626 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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 com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class ClassGen {
|
||||
|
||||
private final ClassNode cls;
|
||||
private final ClassGen parentGen;
|
||||
private final AnnotationGen annotationGen;
|
||||
private final boolean fallback;
|
||||
private final boolean useImports;
|
||||
private final boolean showInconsistentCode;
|
||||
|
||||
private final Set<ClassInfo> imports = new HashSet<>();
|
||||
private int clsDeclLine;
|
||||
|
||||
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
|
||||
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||
}
|
||||
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen) {
|
||||
this(cls, parentClsGen, parentClsGen.useImports, parentClsGen.fallback, parentClsGen.showInconsistentCode);
|
||||
}
|
||||
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean useImports, boolean fallback, boolean showBadCode) {
|
||||
this.cls = cls;
|
||||
this.parentGen = parentClsGen;
|
||||
this.fallback = fallback;
|
||||
this.useImports = useImports;
|
||||
this.showInconsistentCode = showBadCode;
|
||||
|
||||
this.annotationGen = new AnnotationGen(cls, this);
|
||||
}
|
||||
|
||||
public ClassNode getClassNode() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public CodeWriter makeClass() throws CodegenException {
|
||||
CodeWriter clsBody = new CodeWriter();
|
||||
addClassCode(clsBody);
|
||||
|
||||
CodeWriter clsCode = new CodeWriter();
|
||||
if (!"".equals(cls.getPackage())) {
|
||||
clsCode.add("package ").add(cls.getPackage()).add(';');
|
||||
clsCode.newLine();
|
||||
}
|
||||
int importsCount = imports.size();
|
||||
if (importsCount != 0) {
|
||||
List<ClassInfo> sortedImports = new ArrayList<>(imports);
|
||||
sortedImports.sort(Comparator.comparing(ClassInfo::getAliasFullName));
|
||||
sortedImports.forEach(classInfo -> {
|
||||
clsCode.startLine("import ");
|
||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
clsCode.attachAnnotation(classNode);
|
||||
}
|
||||
clsCode.add(classInfo.getAliasFullName());
|
||||
clsCode.add(';');
|
||||
});
|
||||
clsCode.newLine();
|
||||
imports.clear();
|
||||
}
|
||||
clsCode.add(clsBody);
|
||||
return clsCode.finish();
|
||||
}
|
||||
|
||||
public void addClassCode(CodeWriter code) throws CodegenException {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
insertDecompilationProblems(code, cls);
|
||||
addClassDeclaration(code);
|
||||
addClassBody(code);
|
||||
}
|
||||
|
||||
public void addClassDeclaration(CodeWriter clsCode) {
|
||||
AccessInfo af = cls.getAccessFlags();
|
||||
if (af.isInterface()) {
|
||||
af = af.remove(AccessFlags.ACC_ABSTRACT)
|
||||
.remove(AccessFlags.ACC_STATIC);
|
||||
} else if (af.isEnum()) {
|
||||
af = af.remove(AccessFlags.ACC_FINAL)
|
||||
.remove(AccessFlags.ACC_ABSTRACT)
|
||||
.remove(AccessFlags.ACC_STATIC);
|
||||
}
|
||||
|
||||
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
|
||||
}
|
||||
|
||||
annotationGen.addForClass(clsCode);
|
||||
insertRenameInfo(clsCode, cls);
|
||||
CodeGenUtils.addSourceFileInfo(clsCode, cls);
|
||||
clsCode.startLine(af.makeString());
|
||||
if (af.isInterface()) {
|
||||
if (af.isAnnotation()) {
|
||||
clsCode.add('@');
|
||||
}
|
||||
clsCode.add("interface ");
|
||||
} else if (af.isEnum()) {
|
||||
clsCode.add("enum ");
|
||||
} else {
|
||||
clsCode.add("class ");
|
||||
}
|
||||
clsCode.attachDefinition(cls);
|
||||
clsCode.add(cls.getClassInfo().getAliasShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenericMap(), true);
|
||||
clsCode.add(' ');
|
||||
|
||||
ArgType sup = cls.getSuperClass();
|
||||
if (sup != null
|
||||
&& !sup.equals(ArgType.OBJECT)
|
||||
&& !sup.getObject().equals(ArgType.ENUM.getObject())) {
|
||||
clsCode.add("extends ");
|
||||
useClass(clsCode, sup);
|
||||
clsCode.add(' ');
|
||||
}
|
||||
|
||||
if (!cls.getInterfaces().isEmpty() && !af.isAnnotation()) {
|
||||
if (cls.getAccessFlags().isInterface()) {
|
||||
clsCode.add("extends ");
|
||||
} else {
|
||||
clsCode.add("implements ");
|
||||
}
|
||||
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext();) {
|
||||
ArgType interf = it.next();
|
||||
useClass(clsCode, interf);
|
||||
if (it.hasNext()) {
|
||||
clsCode.add(", ");
|
||||
}
|
||||
}
|
||||
if (!cls.getInterfaces().isEmpty()) {
|
||||
clsCode.add(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap, boolean classDeclaration) {
|
||||
if (gmap == null || gmap.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
code.add('<');
|
||||
int i = 0;
|
||||
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
|
||||
ArgType type = e.getKey();
|
||||
List<ArgType> list = e.getValue();
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
useClass(code, type);
|
||||
}
|
||||
if (list != null && !list.isEmpty()) {
|
||||
code.add(" extends ");
|
||||
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
|
||||
ArgType g = it.next();
|
||||
if (g.isGenericType()) {
|
||||
code.add(g.getObject());
|
||||
} else {
|
||||
useClass(code, g);
|
||||
if (classDeclaration
|
||||
&& !cls.getClassInfo().isInner()
|
||||
&& cls.root().getArgs().isUseImports()) {
|
||||
addImport(ClassInfo.fromType(cls.root(), g));
|
||||
}
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(" & ");
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
code.add('>');
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
||||
clsCode.add('{');
|
||||
clsDeclLine = clsCode.getLine();
|
||||
clsCode.incIndent();
|
||||
addFields(clsCode);
|
||||
addInnerClasses(clsCode, cls);
|
||||
addMethods(clsCode);
|
||||
clsCode.decIndent();
|
||||
clsCode.startLine('}');
|
||||
}
|
||||
|
||||
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
if (innerCls.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
||||
code.newLine();
|
||||
inClGen.addClassCode(code);
|
||||
imports.addAll(inClGen.getImports());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInnerClassesPresents() {
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
if (!innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addMethods(CodeWriter code) {
|
||||
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
|
||||
for (MethodNode mth : methods) {
|
||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
if (code.getLine() != clsDeclLine) {
|
||||
code.newLine();
|
||||
}
|
||||
int savedIndent = code.getIndent();
|
||||
try {
|
||||
addMethod(code, mth);
|
||||
} catch (Exception e) {
|
||||
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||
throw new JadxRuntimeException("Method generation error", e);
|
||||
}
|
||||
code.newLine().add("/*");
|
||||
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
Utils.appendStackTrace(code, e);
|
||||
code.newLine().add("*/");
|
||||
code.setIndent(savedIndent);
|
||||
mth.addError("Method generation error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
|
||||
List<MethodNode> out = new ArrayList<>(methods);
|
||||
out.sort(Comparator.comparingInt(LineAttrNode::getSourceLine));
|
||||
return out;
|
||||
}
|
||||
|
||||
private boolean isMethodsPresents() {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (!mth.contains(AFlag.DONT_GENERATE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||
CodeGenUtils.addComments(code, mth);
|
||||
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
mthGen.addDefinition(code);
|
||||
code.add(';');
|
||||
} else {
|
||||
insertDecompilationProblems(code, mth);
|
||||
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
||||
if (badCode && showInconsistentCode) {
|
||||
mth.remove(AFlag.INCONSISTENT_CODE);
|
||||
badCode = false;
|
||||
}
|
||||
MethodGen mthGen;
|
||||
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
|
||||
mthGen = MethodGen.getFallbackMethodGen(mth);
|
||||
} else {
|
||||
mthGen = new MethodGen(this, mth);
|
||||
}
|
||||
if (mthGen.addDefinition(code)) {
|
||||
code.add(' ');
|
||||
}
|
||||
code.add('{');
|
||||
code.incIndent();
|
||||
mthGen.addInstructions(code);
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
}
|
||||
}
|
||||
|
||||
public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
|
||||
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
|
||||
if (!errors.isEmpty()) {
|
||||
errors.stream().distinct().sorted().forEach(err -> {
|
||||
code.startLine("/* JADX ERROR: ").add(err.getError());
|
||||
Throwable cause = err.getCause();
|
||||
if (cause != null) {
|
||||
code.incIndent();
|
||||
Utils.appendStackTrace(code, cause);
|
||||
code.decIndent();
|
||||
}
|
||||
code.add("*/");
|
||||
});
|
||||
}
|
||||
List<String> warns = node.getAll(AType.JADX_WARN);
|
||||
if (!warns.isEmpty()) {
|
||||
warns.stream().distinct().sorted()
|
||||
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
|
||||
}
|
||||
}
|
||||
|
||||
private void addFields(CodeWriter code) throws CodegenException {
|
||||
addEnumFields(code);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
addField(code, f);
|
||||
}
|
||||
}
|
||||
|
||||
public void addField(CodeWriter code, FieldNode f) {
|
||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
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.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
|
||||
} else {
|
||||
if (fv.getValueType() == InitType.CONST) {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
} else if (fv.getValueType() == InitType.INSN) {
|
||||
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
|
||||
addInsnBody(insnGen, code, fv.getInsn());
|
||||
}
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
}
|
||||
|
||||
private boolean isFieldsPresents() {
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (!field.contains(AFlag.DONT_GENERATE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addEnumFields(CodeWriter code) throws CodegenException {
|
||||
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
|
||||
if (enumFields == null) {
|
||||
return;
|
||||
}
|
||||
InsnGen igen = null;
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getField().getAlias());
|
||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||
if (constrInsn.getArgsCount() > f.getStartArg()) {
|
||||
if (igen == null) {
|
||||
igen = makeInsnGen(enumFields.getStaticMethod());
|
||||
}
|
||||
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
|
||||
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
|
||||
}
|
||||
if (f.getCls() != null) {
|
||||
code.add(' ');
|
||||
new ClassGen(f.getCls(), this).addClassBody(code);
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(',');
|
||||
}
|
||||
}
|
||||
if (isMethodsPresents() || isFieldsPresents() || isInnerClassesPresents()) {
|
||||
if (enumFields.getFields().isEmpty()) {
|
||||
code.startLine();
|
||||
}
|
||||
code.add(';');
|
||||
if (isFieldsPresents()) {
|
||||
code.startLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
ErrorsCounter.classError(cls, "Failed to generate init code", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void useType(CodeWriter code, ArgType type) {
|
||||
PrimitiveType stype = type.getPrimitiveType();
|
||||
if (stype == null) {
|
||||
code.add(type.toString());
|
||||
} else if (stype == PrimitiveType.OBJECT) {
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
useClass(code, type);
|
||||
}
|
||||
} else if (stype == PrimitiveType.ARRAY) {
|
||||
useType(code, type.getArrayElement());
|
||||
code.add("[]");
|
||||
} else {
|
||||
code.add(stype.getLongName());
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ArgType type) {
|
||||
useClass(code, ClassInfo.fromType(cls.root(), type));
|
||||
ArgType[] generics = type.getGenericTypes();
|
||||
if (generics != null) {
|
||||
code.add('<');
|
||||
int len = generics.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
ArgType gt = generics[i];
|
||||
ArgType wt = gt.getWildcardType();
|
||||
if (wt != null) {
|
||||
code.add('?');
|
||||
int bounds = gt.getWildcardBounds();
|
||||
if (bounds != 0) {
|
||||
code.add(bounds == -1 ? " super " : " extends ");
|
||||
useType(code, wt);
|
||||
}
|
||||
} else {
|
||||
useType(code, gt);
|
||||
}
|
||||
}
|
||||
code.add('>');
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
useClass(code, classNode);
|
||||
} else {
|
||||
addClsName(code, classInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassNode classNode) {
|
||||
code.attachAnnotation(classNode);
|
||||
addClsName(code, classNode.getClassInfo());
|
||||
}
|
||||
|
||||
private void addClsName(CodeWriter code, ClassInfo classInfo) {
|
||||
String clsName = useClassInternal(cls.getClassInfo(), classInfo);
|
||||
code.add(clsName);
|
||||
}
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||
String fullName = extClsInfo.getAliasFullName();
|
||||
if (fallback || !useImports) {
|
||||
return fullName;
|
||||
}
|
||||
String shortName = extClsInfo.getAliasShortName();
|
||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||
return shortName;
|
||||
}
|
||||
if (isClassInnerFor(useCls, extClsInfo)) {
|
||||
return shortName;
|
||||
}
|
||||
if (isBothClassesInOneTopClass(useCls, extClsInfo)) {
|
||||
return shortName;
|
||||
}
|
||||
// don't add import if this class from same package
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
|
||||
return shortName;
|
||||
}
|
||||
// don't add import if class not public (must be accessed using inheritance)
|
||||
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
|
||||
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
||||
return shortName;
|
||||
}
|
||||
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
// ignore classes from default package
|
||||
if (extClsInfo.isDefaultPackage()) {
|
||||
return shortName;
|
||||
}
|
||||
if (extClsInfo.getAliasPkg().equals(useCls.getAliasPkg())) {
|
||||
fullName = extClsInfo.getAliasNameWithoutPackage();
|
||||
}
|
||||
for (ClassInfo importCls : getImports()) {
|
||||
if (!importCls.equals(extClsInfo)
|
||||
&& importCls.getAliasShortName().equals(shortName)) {
|
||||
if (extClsInfo.isInner()) {
|
||||
String parent = useClassInternal(useCls, extClsInfo.getParentClass());
|
||||
return parent + '.' + shortName;
|
||||
} else {
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
addImport(extClsInfo);
|
||||
return shortName;
|
||||
}
|
||||
|
||||
private void addImport(ClassInfo classInfo) {
|
||||
if (parentGen != null) {
|
||||
parentGen.addImport(classInfo);
|
||||
} else {
|
||||
imports.add(classInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<ClassInfo> getImports() {
|
||||
if (parentGen != null) {
|
||||
return parentGen.getImports();
|
||||
} else {
|
||||
return imports;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isBothClassesInOneTopClass(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||
ClassInfo a = useCls.getTopParentClass();
|
||||
ClassInfo b = extClsInfo.getTopParentClass();
|
||||
if (a != null) {
|
||||
return a.equals(b);
|
||||
}
|
||||
// useCls - is a top class
|
||||
return useCls.equals(b);
|
||||
}
|
||||
|
||||
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
|
||||
if (inner.isInner()) {
|
||||
ClassInfo p = inner.getParentClass();
|
||||
return p.equals(parent) || isClassInnerFor(p, parent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
|
||||
if (useCls == null) {
|
||||
return false;
|
||||
}
|
||||
String shortName = searchCls.getAliasShortName();
|
||||
if (useCls.getAliasShortName().equals(shortName)) {
|
||||
return true;
|
||||
}
|
||||
ClassNode classNode = dex.resolveClass(useCls);
|
||||
if (classNode != null) {
|
||||
for (ClassNode inner : classNode.getInnerClasses()) {
|
||||
if (inner.getShortName().equals(shortName)
|
||||
&& !inner.getFullName().equals(searchCls.getAliasFullName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchCollision(dex, useCls.getParentClass(), searchCls);
|
||||
}
|
||||
|
||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (classInfo.hasAlias()) {
|
||||
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
public ClassGen getParentGen() {
|
||||
return parentGen == null ? this : parentGen;
|
||||
}
|
||||
|
||||
public AnnotationGen getAnnotationGen() {
|
||||
return annotationGen;
|
||||
}
|
||||
|
||||
public boolean isFallbackMode() {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.json.JsonCodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class CodeGen {
|
||||
|
||||
public static void generate(ClassNode cls) {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
cls.setCode(CodeWriter.EMPTY);
|
||||
} else {
|
||||
JadxArgs args = cls.root().getArgs();
|
||||
switch (args.getOutputFormat()) {
|
||||
case JAVA:
|
||||
generateJavaCode(cls, args);
|
||||
break;
|
||||
|
||||
case JSON:
|
||||
generateJson(cls);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void generateJavaCode(ClassNode cls, JadxArgs args) {
|
||||
ClassGen clsGen = new ClassGen(cls, args);
|
||||
CodeWriter code = wrapCodeGen(cls, clsGen::makeClass);
|
||||
cls.setCode(code);
|
||||
}
|
||||
|
||||
private static void generateJson(ClassNode cls) {
|
||||
JsonCodeGen codeGen = new JsonCodeGen(cls);
|
||||
String clsJson = wrapCodeGen(cls, codeGen::process);
|
||||
cls.setCode(new CodeWriter(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() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
|
||||
public class CodeWriter {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
|
||||
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
public static final String INDENT_STR = " ";
|
||||
|
||||
public static final CodeWriter EMPTY = new CodeWriter().finish();
|
||||
|
||||
private static final boolean ADD_LINE_NUMBERS = false;
|
||||
|
||||
private static final String[] INDENT_CACHE = {
|
||||
"",
|
||||
INDENT_STR,
|
||||
INDENT_STR + INDENT_STR,
|
||||
INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
};
|
||||
|
||||
private StringBuilder buf;
|
||||
@Nullable
|
||||
private String code;
|
||||
private String indentStr;
|
||||
private int indent;
|
||||
|
||||
private int line = 1;
|
||||
private int offset = 0;
|
||||
private Map<CodePosition, Object> annotations = Collections.emptyMap();
|
||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||
|
||||
public CodeWriter() {
|
||||
this.buf = new StringBuilder();
|
||||
this.indent = 0;
|
||||
this.indentStr = "";
|
||||
if (ADD_LINE_NUMBERS) {
|
||||
incIndent(2);
|
||||
}
|
||||
}
|
||||
|
||||
// create filled instance (just string wrapper)
|
||||
public CodeWriter(String code) {
|
||||
this.buf = null;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public CodeWriter startLine() {
|
||||
addLine();
|
||||
addLineIndent();
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter startLine(char c) {
|
||||
addLine();
|
||||
addLineIndent();
|
||||
add(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter startLine(String str) {
|
||||
addLine();
|
||||
addLineIndent();
|
||||
add(str);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter startLineWithNum(int sourceLine) {
|
||||
if (sourceLine == 0) {
|
||||
startLine();
|
||||
return this;
|
||||
}
|
||||
if (ADD_LINE_NUMBERS) {
|
||||
newLine();
|
||||
attachSourceLine(sourceLine);
|
||||
String ln = "/* " + sourceLine + " */ ";
|
||||
add(ln);
|
||||
if (indentStr.length() > ln.length()) {
|
||||
add(indentStr.substring(ln.length()));
|
||||
}
|
||||
} else {
|
||||
startLine();
|
||||
attachSourceLine(sourceLine);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter addMultiLine(String str) {
|
||||
if (str.contains(NL)) {
|
||||
buf.append(str.replace(NL, NL + indentStr));
|
||||
line += StringUtils.countMatches(str, NL);
|
||||
offset = 0;
|
||||
} else {
|
||||
buf.append(str);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(String str) {
|
||||
buf.append(str);
|
||||
offset += str.length();
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(char c) {
|
||||
buf.append(c);
|
||||
offset++;
|
||||
return this;
|
||||
}
|
||||
|
||||
CodeWriter add(CodeWriter code) {
|
||||
line--;
|
||||
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
||||
CodePosition pos = entry.getKey();
|
||||
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
|
||||
}
|
||||
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||
}
|
||||
line += code.line;
|
||||
offset = code.offset;
|
||||
buf.append(code.buf);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter newLine() {
|
||||
addLine();
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter addIndent() {
|
||||
add(INDENT_STR);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addLine() {
|
||||
buf.append(NL);
|
||||
line++;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
private CodeWriter addLineIndent() {
|
||||
buf.append(indentStr);
|
||||
offset += indentStr.length();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void updateIndent() {
|
||||
int curIndent = indent;
|
||||
if (curIndent < INDENT_CACHE.length) {
|
||||
this.indentStr = INDENT_CACHE[curIndent];
|
||||
} else {
|
||||
StringBuilder s = new StringBuilder(curIndent * INDENT_STR.length());
|
||||
for (int i = 0; i < curIndent; i++) {
|
||||
s.append(INDENT_STR);
|
||||
}
|
||||
this.indentStr = s.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public void incIndent() {
|
||||
incIndent(1);
|
||||
}
|
||||
|
||||
public void decIndent() {
|
||||
decIndent(1);
|
||||
}
|
||||
|
||||
public void incIndent(int c) {
|
||||
this.indent += c;
|
||||
updateIndent();
|
||||
}
|
||||
|
||||
public void decIndent(int c) {
|
||||
this.indent -= c;
|
||||
if (this.indent < 0) {
|
||||
LOG.warn("Indent < 0");
|
||||
this.indent = 0;
|
||||
}
|
||||
updateIndent();
|
||||
}
|
||||
|
||||
public int getIndent() {
|
||||
return indent;
|
||||
}
|
||||
|
||||
public void setIndent(int indent) {
|
||||
this.indent = indent;
|
||||
updateIndent();
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
private static class DefinitionWrapper {
|
||||
private final LineAttrNode node;
|
||||
|
||||
private DefinitionWrapper(LineAttrNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public LineAttrNode getNode() {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
public void attachDefinition(LineAttrNode obj) {
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
}
|
||||
|
||||
public void attachAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
}
|
||||
|
||||
public void attachLineAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, 0));
|
||||
}
|
||||
|
||||
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||
if (annotations.isEmpty()) {
|
||||
annotations = new HashMap<>();
|
||||
}
|
||||
return annotations.put(pos, obj);
|
||||
}
|
||||
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void attachSourceLine(int sourceLine) {
|
||||
if (sourceLine == 0) {
|
||||
return;
|
||||
}
|
||||
attachSourceLine(line, sourceLine);
|
||||
}
|
||||
|
||||
private void attachSourceLine(int decompiledLine, int sourceLine) {
|
||||
if (lineMap.isEmpty()) {
|
||||
lineMap = new TreeMap<>();
|
||||
}
|
||||
lineMap.put(decompiledLine, sourceLine);
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lineMap;
|
||||
}
|
||||
|
||||
public CodeWriter finish() {
|
||||
removeFirstEmptyLine();
|
||||
buf.trimToSize();
|
||||
code = buf.toString();
|
||||
buf = null;
|
||||
|
||||
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 this;
|
||||
}
|
||||
|
||||
private void removeFirstEmptyLine() {
|
||||
int len = NL.length();
|
||||
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
||||
buf.delete(0, len);
|
||||
}
|
||||
}
|
||||
|
||||
public int bufLength() {
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
public String getCodeStr() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return buf == null ? code : buf.toString();
|
||||
}
|
||||
|
||||
public void save(File dir, String subDir, String fileName) {
|
||||
if (!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
|
||||
return;
|
||||
}
|
||||
save(dir, new File(subDir, fileName).getPath());
|
||||
}
|
||||
|
||||
public void save(File dir, String fileName) {
|
||||
if (!ZipSecurity.isValidZipEntryName(fileName)) {
|
||||
return;
|
||||
}
|
||||
save(new File(dir, fileName));
|
||||
}
|
||||
|
||||
public void save(File file) {
|
||||
if (code == null) {
|
||||
finish();
|
||||
}
|
||||
File outFile = FileUtils.prepareFile(file);
|
||||
try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) {
|
||||
out.println(code);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Save file error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.conditions.Compare;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class ConditionGen extends InsnGen {
|
||||
|
||||
private static class CondStack {
|
||||
private final Queue<IfCondition> stack = new LinkedList<>();
|
||||
|
||||
public Queue<IfCondition> getStack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
public void push(IfCondition cond) {
|
||||
stack.add(cond);
|
||||
}
|
||||
|
||||
public IfCondition pop() {
|
||||
return stack.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public ConditionGen(InsnGen insnGen) {
|
||||
super(insnGen.mgen, insnGen.fallback);
|
||||
}
|
||||
|
||||
void add(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
add(code, new CondStack(), condition);
|
||||
}
|
||||
|
||||
void wrap(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
wrap(code, new CondStack(), condition);
|
||||
}
|
||||
|
||||
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||
stack.push(condition);
|
||||
switch (condition.getMode()) {
|
||||
case COMPARE:
|
||||
addCompare(code, stack, condition.getCompare());
|
||||
break;
|
||||
|
||||
case TERNARY:
|
||||
addTernary(code, stack, condition);
|
||||
break;
|
||||
|
||||
case NOT:
|
||||
addNot(code, stack, condition);
|
||||
break;
|
||||
|
||||
case AND:
|
||||
case OR:
|
||||
addAndOr(code, stack, condition);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown condition mode: " + condition.getMode());
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
|
||||
boolean wrap = isWrapNeeded(cond);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
add(code, stack, cond);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException {
|
||||
boolean wrap = isArgWrapNeeded(firstArg);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
addArg(code, firstArg, false);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException {
|
||||
IfOp op = compare.getOp();
|
||||
InsnArg firstArg = compare.getA();
|
||||
InsnArg secondArg = compare.getB();
|
||||
if (firstArg.getType().equals(ArgType.BOOLEAN)
|
||||
&& secondArg.isLiteral()
|
||||
&& secondArg.getType().equals(ArgType.BOOLEAN)) {
|
||||
LiteralArg lit = (LiteralArg) secondArg;
|
||||
if (lit.getLiteral() == 0) {
|
||||
op = op.invert();
|
||||
}
|
||||
if (op == IfOp.EQ) {
|
||||
// == true
|
||||
if (stack.getStack().size() == 1) {
|
||||
addArg(code, firstArg, false);
|
||||
} else {
|
||||
wrap(code, firstArg);
|
||||
}
|
||||
return;
|
||||
} else if (op == IfOp.NE) {
|
||||
// != true
|
||||
code.add('!');
|
||||
wrap(code, firstArg);
|
||||
return;
|
||||
}
|
||||
ErrorsCounter.methodWarn(mth, "Unsupported boolean condition " + op.getSymbol());
|
||||
}
|
||||
|
||||
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
||||
code.add(' ').add(op.getSymbol()).add(' ');
|
||||
addArg(code, secondArg, isArgWrapNeeded(secondArg));
|
||||
}
|
||||
|
||||
private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||
add(code, stack, condition.first());
|
||||
code.add(" ? ");
|
||||
add(code, stack, condition.second());
|
||||
code.add(" : ");
|
||||
add(code, stack, condition.third());
|
||||
}
|
||||
|
||||
private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||
code.add('!');
|
||||
wrap(code, stack, condition.getArgs().get(0));
|
||||
}
|
||||
|
||||
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
|
||||
Iterator<IfCondition> it = condition.getArgs().iterator();
|
||||
while (it.hasNext()) {
|
||||
wrap(code, stack, it.next());
|
||||
if (it.hasNext()) {
|
||||
code.add(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWrapNeeded(IfCondition condition) {
|
||||
if (condition.isCompare() || condition.contains(AFlag.DONT_WRAP)) {
|
||||
return false;
|
||||
}
|
||||
return condition.getMode() != Mode.NOT;
|
||||
}
|
||||
|
||||
private static boolean isArgWrapNeeded(InsnArg arg) {
|
||||
if (!arg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
InsnType insnType = insn.getType();
|
||||
if (insnType == InsnType.ARITH) {
|
||||
switch (((ArithNode) insn).getOp()) {
|
||||
case ADD:
|
||||
case SUB:
|
||||
case MUL:
|
||||
case DIV:
|
||||
case REM:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
switch (insnType) {
|
||||
case INVOKE:
|
||||
case SGET:
|
||||
case IGET:
|
||||
case AGET:
|
||||
case CONST:
|
||||
case ARRAY_LENGTH:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,989 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ArithOp;
|
||||
import jadx.core.dex.instructions.ConstClassNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
import jadx.core.dex.instructions.FillArrayNode;
|
||||
import jadx.core.dex.instructions.FilledNewArrayNode;
|
||||
import jadx.core.dex.instructions.GotoNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.NewArrayNode;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.FieldArg;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.Named;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
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.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.android.AndroidResourcesUtils.handleAppResField;
|
||||
|
||||
public class InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
|
||||
|
||||
protected final MethodGen mgen;
|
||||
protected final MethodNode mth;
|
||||
protected final RootNode root;
|
||||
protected final boolean fallback;
|
||||
protected final boolean attachInsns;
|
||||
|
||||
protected enum Flags {
|
||||
BODY_ONLY,
|
||||
BODY_ONLY_NOWRAP,
|
||||
INLINE
|
||||
}
|
||||
|
||||
public InsnGen(MethodGen mgen, boolean fallback) {
|
||||
this.mgen = mgen;
|
||||
this.mth = mgen.getMethodNode();
|
||||
this.root = mth.root();
|
||||
this.fallback = fallback;
|
||||
this.attachInsns = root.getArgs().isJsonOutput();
|
||||
}
|
||||
|
||||
private boolean isFallback() {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||
int len = code.bufLength();
|
||||
addArg(code, arg, true);
|
||||
if (len != code.bufLength()) {
|
||||
code.add('.');
|
||||
}
|
||||
}
|
||||
|
||||
public void addArg(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||
addArg(code, arg, true);
|
||||
}
|
||||
|
||||
public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
|
||||
if (arg.isRegister()) {
|
||||
code.add(mgen.getNameGen().useArg((RegisterArg) arg));
|
||||
} else if (arg.isLiteral()) {
|
||||
code.add(lit((LiteralArg) arg));
|
||||
} else if (arg.isInsnWrap()) {
|
||||
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
||||
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
|
||||
} else if (arg.isNamed()) {
|
||||
code.add(((Named) arg).getName());
|
||||
} else if (arg.isField()) {
|
||||
FieldArg f = (FieldArg) arg;
|
||||
if (f.isStatic()) {
|
||||
staticField(code, f.getField());
|
||||
} else {
|
||||
instanceField(code, f.getField(), f.getInstanceArg());
|
||||
}
|
||||
} else {
|
||||
throw new CodegenException("Unknown arg type " + arg);
|
||||
}
|
||||
}
|
||||
|
||||
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
|
||||
RegisterArg arg = insn.getResult();
|
||||
if (insn.contains(AFlag.DECLARE_VAR)) {
|
||||
declareVar(code, arg);
|
||||
} else {
|
||||
addArg(code, arg, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
declareVar(code, arg.getSVar().getCodeVar());
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, CodeVar codeVar) {
|
||||
if (codeVar.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
useType(code, codeVar.getType());
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth, fallback);
|
||||
}
|
||||
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
ClassNode pCls = mth.getParentClass();
|
||||
FieldNode fieldNode = pCls.dex().root().deepResolveField(field);
|
||||
if (fieldNode != null) {
|
||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||
if (replace != null) {
|
||||
switch (replace.getReplaceType()) {
|
||||
case CLASS_INSTANCE:
|
||||
useClass(code, replace.getClsRef());
|
||||
code.add(".this");
|
||||
break;
|
||||
case VAR:
|
||||
addArg(code, replace.getVarRef());
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
addArgDot(code, arg);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
if (fieldNode == null) {
|
||||
code.add(field.getAlias());
|
||||
} else {
|
||||
code.add(fieldNode.getAlias());
|
||||
}
|
||||
}
|
||||
|
||||
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||
ClassInfo declClass = field.getDeclClass();
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||
if (!fieldFromThisClass) {
|
||||
// Android specific resources class handler
|
||||
if (!handleAppResField(code, clsGen, declClass)) {
|
||||
clsGen.useClass(code, declClass);
|
||||
}
|
||||
code.add('.');
|
||||
}
|
||||
FieldNode fieldNode = clsGen.getClassNode().dex().root().deepResolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
if (fieldNode == null) {
|
||||
code.add(field.getAlias());
|
||||
} else {
|
||||
code.add(fieldNode.getAlias());
|
||||
}
|
||||
}
|
||||
|
||||
protected void staticField(CodeWriter code, FieldInfo field) {
|
||||
makeStaticFieldAccess(code, field, mgen.getClassGen());
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ArgType type) {
|
||||
mgen.getClassGen().useClass(code, type);
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassInfo cls) {
|
||||
mgen.getClassGen().useClass(code, cls);
|
||||
}
|
||||
|
||||
protected void useType(CodeWriter code, ArgType type) {
|
||||
mgen.getClassGen().useType(code, type);
|
||||
}
|
||||
|
||||
public void makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
makeInsn(insn, code, null);
|
||||
}
|
||||
|
||||
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 {
|
||||
try {
|
||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
|
||||
} else {
|
||||
if (flag != Flags.INLINE) {
|
||||
code.startLineWithNum(insn.getSourceLine());
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
}
|
||||
}
|
||||
if (insn.getResult() != null) {
|
||||
SSAVar var = insn.getResult().getSVar();
|
||||
if ((var == null || var.getUseCount() != 0 || insn.getType() != InsnType.CONSTRUCTOR)
|
||||
&& !insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
assignVar(code, insn);
|
||||
code.add(" = ");
|
||||
}
|
||||
}
|
||||
makeInsnBody(code, insn, EMPTY_FLAGS);
|
||||
if (flag != Flags.INLINE) {
|
||||
code.add(';');
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new CodegenException(mth, "Error generate insn: " + insn, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
||||
switch (insn.getType()) {
|
||||
case CONST_STR:
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
code.add(mth.dex().root().getStringUtils().unescapeString(str));
|
||||
break;
|
||||
|
||||
case CONST_CLASS:
|
||||
ArgType clsType = ((ConstClassNode) insn).getClsType();
|
||||
useType(code, clsType);
|
||||
code.add(".class");
|
||||
break;
|
||||
|
||||
case CONST:
|
||||
LiteralArg arg = (LiteralArg) insn.getArg(0);
|
||||
code.add(lit(arg));
|
||||
break;
|
||||
|
||||
case MOVE:
|
||||
addArg(code, insn.getArg(0), false);
|
||||
break;
|
||||
|
||||
case CHECK_CAST:
|
||||
case CAST: {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
code.add('(');
|
||||
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
|
||||
code.add(") ");
|
||||
addArg(code, insn.getArg(0), true);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ARITH:
|
||||
makeArith((ArithNode) insn, code, state);
|
||||
break;
|
||||
|
||||
case NEG:
|
||||
oneArgInsn(code, insn, state, '-');
|
||||
break;
|
||||
|
||||
case NOT:
|
||||
char op = insn.getArg(0).getType() == ArgType.BOOLEAN ? '!' : '~';
|
||||
oneArgInsn(code, insn, state, op);
|
||||
break;
|
||||
|
||||
case RETURN:
|
||||
if (insn.getArgsCount() != 0) {
|
||||
code.add("return ");
|
||||
addArg(code, insn.getArg(0), false);
|
||||
} else {
|
||||
code.add("return");
|
||||
}
|
||||
break;
|
||||
|
||||
case BREAK:
|
||||
code.add("break");
|
||||
LoopLabelAttr labelAttr = insn.get(AType.LOOP_LABEL);
|
||||
if (labelAttr != null) {
|
||||
code.add(' ').add(mgen.getNameGen().getLoopLabel(labelAttr));
|
||||
}
|
||||
break;
|
||||
|
||||
case CONTINUE:
|
||||
code.add("continue");
|
||||
break;
|
||||
|
||||
case THROW:
|
||||
code.add("throw ");
|
||||
addArg(code, insn.getArg(0), true);
|
||||
break;
|
||||
|
||||
case CMP_L:
|
||||
case CMP_G:
|
||||
code.add('(');
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" > ");
|
||||
addArg(code, insn.getArg(1));
|
||||
code.add(" ? 1 : (");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" == ");
|
||||
addArg(code, insn.getArg(1));
|
||||
code.add(" ? 0 : -1))");
|
||||
break;
|
||||
|
||||
case INSTANCE_OF: {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" instanceof ");
|
||||
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONSTRUCTOR:
|
||||
makeConstructor((ConstructorInsn) insn, code);
|
||||
break;
|
||||
|
||||
case INVOKE:
|
||||
makeInvoke((InvokeNode) insn, code);
|
||||
break;
|
||||
|
||||
case NEW_ARRAY: {
|
||||
ArgType arrayType = ((NewArrayNode) insn).getArrayType();
|
||||
code.add("new ");
|
||||
useType(code, arrayType.getArrayRootElement());
|
||||
code.add('[');
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(']');
|
||||
int dim = arrayType.getArrayDimension();
|
||||
for (int i = 0; i < dim - 1; i++) {
|
||||
code.add("[]");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ARRAY_LENGTH:
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(".length");
|
||||
break;
|
||||
|
||||
case FILLED_NEW_ARRAY:
|
||||
filledNewArray((FilledNewArrayNode) insn, code);
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||
if (fallback) {
|
||||
String arrStr = arrayNode.dataToString();
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" = {").add(arrStr.substring(1, arrStr.length() - 1)).add("} // fill-array");
|
||||
} else {
|
||||
fillArray(code, arrayNode);
|
||||
}
|
||||
break;
|
||||
|
||||
case AGET:
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add('[');
|
||||
addArg(code, insn.getArg(1), false);
|
||||
code.add(']');
|
||||
break;
|
||||
|
||||
case APUT:
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add('[');
|
||||
addArg(code, insn.getArg(1), false);
|
||||
code.add("] = ");
|
||||
addArg(code, insn.getArg(2), false);
|
||||
break;
|
||||
|
||||
case IGET: {
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
instanceField(code, fieldInfo, insn.getArg(0));
|
||||
break;
|
||||
}
|
||||
case IPUT: {
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
instanceField(code, fieldInfo, insn.getArg(1));
|
||||
code.add(" = ");
|
||||
addArg(code, insn.getArg(0), false);
|
||||
break;
|
||||
}
|
||||
|
||||
case SGET:
|
||||
staticField(code, (FieldInfo) ((IndexInsnNode) insn).getIndex());
|
||||
break;
|
||||
case SPUT:
|
||||
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
staticField(code, field);
|
||||
code.add(" = ");
|
||||
addArg(code, insn.getArg(0), false);
|
||||
break;
|
||||
|
||||
case STR_CONCAT:
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext();) {
|
||||
addArg(code, it.next());
|
||||
if (it.hasNext()) {
|
||||
code.add(" + ");
|
||||
}
|
||||
}
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
|
||||
case MONITOR_ENTER:
|
||||
if (isFallback()) {
|
||||
code.add("monitor-enter(");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
|
||||
case MONITOR_EXIT:
|
||||
if (isFallback()) {
|
||||
code.add("monitor-exit(");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
|
||||
case TERNARY:
|
||||
makeTernary((TernaryInsn) insn, code, state);
|
||||
break;
|
||||
|
||||
case ONE_ARG:
|
||||
addArg(code, insn.getArg(0));
|
||||
break;
|
||||
|
||||
/* fallback mode instructions */
|
||||
case IF:
|
||||
fallbackOnlyInsn(insn);
|
||||
IfNode ifInsn = (IfNode) insn;
|
||||
code.add("if (");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(' ');
|
||||
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
||||
addArg(code, insn.getArg(1));
|
||||
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
|
||||
break;
|
||||
|
||||
case GOTO:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
|
||||
break;
|
||||
|
||||
case MOVE_EXCEPTION:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("move-exception");
|
||||
break;
|
||||
|
||||
case SWITCH:
|
||||
fallbackOnlyInsn(insn);
|
||||
SwitchNode sw = (SwitchNode) insn;
|
||||
code.add("switch(");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
for (int i = 0; i < sw.getCasesCount(); i++) {
|
||||
String key = sw.getKeys()[i].toString();
|
||||
code.startLine("case ").add(key).add(": goto ");
|
||||
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
|
||||
}
|
||||
code.startLine("default: goto ");
|
||||
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
break;
|
||||
|
||||
case NEW_INSTANCE:
|
||||
// only fallback - make new instance in constructor invoke
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("new ").add(insn.getResult().getInitType().toString());
|
||||
break;
|
||||
|
||||
case PHI:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add(insn.getType().toString()).add('(');
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
addArg(code, insnArg);
|
||||
code.add(' ');
|
||||
}
|
||||
code.add(')');
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, FillArrayNode arrayNode) throws CodegenException {
|
||||
code.add("// fill-array-data instruction");
|
||||
code.startLine();
|
||||
List<LiteralArg> args = arrayNode.getLiteralArgs(arrayNode.getElementType());
|
||||
InsnArg arrArg = arrayNode.getArg(0);
|
||||
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 {
|
||||
if (!insn.contains(AFlag.DECLARE_VAR)) {
|
||||
code.add("new ");
|
||||
useType(code, insn.getArrayType());
|
||||
}
|
||||
code.add('{');
|
||||
int c = insn.getArgsCount();
|
||||
for (int i = 0; i < c; i++) {
|
||||
addArg(code, insn.getArg(i), false);
|
||||
if (i + 1 < c) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add('}');
|
||||
}
|
||||
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||
inlineAnonymousConstructor(code, cls, insn);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
||||
}
|
||||
if (insn.isSuper()) {
|
||||
code.add("super");
|
||||
} else if (insn.isThis()) {
|
||||
code.add("this");
|
||||
} else {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
|
||||
if (argType.isGeneric()) {
|
||||
code.add('<');
|
||||
if (insn.contains(AFlag.EXPLICIT_GENERICS)) {
|
||||
boolean first = true;
|
||||
for (ArgType type : argType.getGenericTypes()) {
|
||||
if (!first) {
|
||||
code.add(',');
|
||||
}
|
||||
mgen.getClassGen().useType(code, type);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
code.add('>');
|
||||
}
|
||||
}
|
||||
MethodNode callMth = mth.dex().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.dex().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
code.add(' ');
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||
}
|
||||
|
||||
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
MethodInfo callMth = insn.getCallMth();
|
||||
|
||||
// inline method
|
||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
||||
if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
InvokeType type = insn.getInvokeType();
|
||||
switch (type) {
|
||||
case DIRECT:
|
||||
case VIRTUAL:
|
||||
case INTERFACE:
|
||||
InsnArg arg = insn.getArg(0);
|
||||
// FIXME: add 'this' for equals methods in scope
|
||||
if (!arg.isThis()) {
|
||||
addArgDot(code, arg);
|
||||
}
|
||||
k++;
|
||||
break;
|
||||
|
||||
case SUPER:
|
||||
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
|
||||
if (superCallCls != null) {
|
||||
useClass(code, superCallCls);
|
||||
code.add('.');
|
||||
}
|
||||
// use 'super' instead 'this' in 0 arg
|
||||
code.add("super").add('.');
|
||||
k++;
|
||||
break;
|
||||
|
||||
case STATIC:
|
||||
ClassInfo insnCls = mth.getParentClass().getClassInfo();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (!insnCls.equals(declClass)) {
|
||||
useClass(code, declClass);
|
||||
code.add('.');
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (callMthNode != null) {
|
||||
code.attachAnnotation(callMthNode);
|
||||
code.add(callMthNode.getAlias());
|
||||
} else {
|
||||
code.add(callMth.getAlias());
|
||||
}
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
|
||||
ClassNode useCls = mth.getParentClass();
|
||||
ClassInfo insnCls = useCls.getClassInfo();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (insnCls.equals(declClass)) {
|
||||
return null;
|
||||
}
|
||||
ClassNode topClass = useCls.getTopParentClass();
|
||||
if (topClass.getClassInfo().equals(declClass)) {
|
||||
return declClass;
|
||||
}
|
||||
// search call class
|
||||
ClassNode nextParent = useCls;
|
||||
do {
|
||||
ClassInfo nextClsInfo = nextParent.getClassInfo();
|
||||
if (nextClsInfo.equals(declClass)
|
||||
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
|
||||
if (nextParent == useCls) {
|
||||
return null;
|
||||
}
|
||||
return nextClsInfo;
|
||||
}
|
||||
nextParent = nextParent.getParentClass();
|
||||
} while (nextParent != null && nextParent != topClass);
|
||||
|
||||
// search failed, just return parent class
|
||||
return useCls.getParentClass().getClassInfo();
|
||||
}
|
||||
|
||||
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
||||
@Nullable MethodNode callMth) throws CodegenException {
|
||||
int k = startArgNum;
|
||||
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
k++;
|
||||
}
|
||||
int argsCount = insn.getArgsCount();
|
||||
code.add('(');
|
||||
boolean firstArg = true;
|
||||
if (k < argsCount) {
|
||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||
for (int i = k; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
RegisterArg callArg = getCallMthArg(callMth, i - startArgNum);
|
||||
if (callArg != null && callArg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
if (!firstArg) {
|
||||
code.add(", ");
|
||||
}
|
||||
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
|
||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
||||
continue;
|
||||
}
|
||||
addArg(code, arg, false);
|
||||
firstArg = false;
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
|
||||
private static RegisterArg getCallMthArg(@Nullable MethodNode callMth, int num) {
|
||||
if (callMth == null) {
|
||||
return null;
|
||||
}
|
||||
List<RegisterArg> args = callMth.getArguments(false);
|
||||
if (args != null && num < args.size()) {
|
||||
return args.get(num);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional cast for overloaded method argument.
|
||||
*/
|
||||
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
|
||||
ArgType origType;
|
||||
List<RegisterArg> arguments = callMth.getArguments(false);
|
||||
if (arguments == null || arguments.isEmpty()) {
|
||||
mth.addComment("JADX INFO: used method not loaded: " + callMth + ", types can be incorrect");
|
||||
origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
|
||||
} else {
|
||||
origType = arguments.get(origPos).getInitType();
|
||||
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
|
||||
// cancel cast
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ArgType argType = arg.getType();
|
||||
if (argType.equals(origType)
|
||||
// null cast to object
|
||||
&& (!arg.isLiteral() || ((LiteralArg) arg).getLiteral() != 0
|
||||
|| (!argType.isArray() && !argType.isObject()))) {
|
||||
return false;
|
||||
}
|
||||
if (origType.isGeneric()) {
|
||||
if (argType.isObject()) {
|
||||
if (!argType.isGeneric() && arg.isInsnWrap()) {
|
||||
((InsnWrapArg) arg).getWrapInsn().getResult().setType(
|
||||
ArgType.generic(argType.getObject(), origType.getGenericTypes()));
|
||||
}
|
||||
if (origType.getObject().equals(argType.getObject())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (arg.isInsnWrap()) {
|
||||
((InsnWrapArg) arg).getWrapInsn().add(AFlag.EXPLICIT_GENERICS);
|
||||
}
|
||||
}
|
||||
code.add('(');
|
||||
useType(code, origType);
|
||||
code.add(") ");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand varArgs from filled array.
|
||||
*/
|
||||
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
|
||||
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
|
||||
return false;
|
||||
}
|
||||
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
||||
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
|
||||
if (mia == null) {
|
||||
return false;
|
||||
}
|
||||
InsnNode inl = mia.getInsn();
|
||||
if (Consts.DEBUG) {
|
||||
code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine();
|
||||
}
|
||||
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
} else {
|
||||
// remap args
|
||||
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
|
||||
List<RegisterArg> callArgs = callMthNode.getArguments(true);
|
||||
for (int i = 0; i < callArgs.size(); i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
RegisterArg callArg = callArgs.get(i);
|
||||
regs[callArg.getRegNum()] = arg;
|
||||
}
|
||||
// replace args
|
||||
InsnNode inlCopy = inl.copy();
|
||||
List<RegisterArg> inlArgs = new ArrayList<>();
|
||||
inlCopy.getRegisterArgs(inlArgs);
|
||||
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 {
|
||||
inlCopy.replaceArg(r, repl);
|
||||
}
|
||||
}
|
||||
}
|
||||
makeInsn(inlCopy, code, Flags.BODY_ONLY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
InsnArg first = insn.getArg(0);
|
||||
InsnArg second = insn.getArg(1);
|
||||
ConditionGen condGen = new ConditionGen(this);
|
||||
if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
|
||||
condGen.add(code, insn.getCondition());
|
||||
} else {
|
||||
condGen.wrap(code, insn.getCondition());
|
||||
code.add(" ? ");
|
||||
addCastIfNeeded(code, first, second);
|
||||
addArg(code, first, false);
|
||||
code.add(" : ");
|
||||
addArg(code, second, false);
|
||||
}
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void addCastIfNeeded(CodeWriter code, InsnArg first, InsnArg second) {
|
||||
if (first.isLiteral() && second.isLiteral()) {
|
||||
if (first.getType() == ArgType.BYTE) {
|
||||
long lit1 = ((LiteralArg) first).getLiteral();
|
||||
long lit2 = ((LiteralArg) second).getLiteral();
|
||||
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
|
||||
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
|
||||
code.add("(byte) ");
|
||||
}
|
||||
} else if (first.getType() == ArgType.SHORT) {
|
||||
long lit1 = ((LiteralArg) first).getLiteral();
|
||||
long lit2 = ((LiteralArg) second).getLiteral();
|
||||
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
|
||||
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
|
||||
code.add("(short) ");
|
||||
}
|
||||
} else if (first.getType() == ArgType.CHAR) {
|
||||
long lit1 = ((LiteralArg) first).getLiteral();
|
||||
long lit2 = ((LiteralArg) second).getLiteral();
|
||||
if (!NameMapper.isPrintableChar((char) (lit1))
|
||||
&& !NameMapper.isPrintableChar((char) (lit2))) {
|
||||
code.add("(char) ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
makeArithOneArg(insn, code);
|
||||
return;
|
||||
}
|
||||
// wrap insn in brackets for save correct operation order
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY) && !insn.contains(AFlag.DONT_WRAP);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(' ');
|
||||
code.add(insn.getOp().getSymbol());
|
||||
code.add(' ');
|
||||
addArg(code, insn.getArg(1));
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
|
||||
ArithOp op = insn.getOp();
|
||||
InsnArg arg = insn.getArg(1);
|
||||
// "++" or "--"
|
||||
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
||||
LiteralArg lit = (LiteralArg) arg;
|
||||
if (lit.isInteger() && lit.getLiteral() == 1) {
|
||||
assignVar(code, insn);
|
||||
String opSymbol = op.getSymbol();
|
||||
code.add(opSymbol).add(opSymbol);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// +=, -= ...
|
||||
assignVar(code, insn);
|
||||
code.add(' ').add(op.getSymbol()).add("= ");
|
||||
addArg(code, arg, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
import jadx.core.Consts;
|
||||
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.info.AccessInfo;
|
||||
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.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.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;
|
||||
|
||||
public class MethodGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||
|
||||
private final MethodNode mth;
|
||||
private final ClassGen classGen;
|
||||
private final AnnotationGen annotationGen;
|
||||
private final NameGen nameGen;
|
||||
|
||||
public MethodGen(ClassGen classGen, MethodNode mth) {
|
||||
this.mth = mth;
|
||||
this.classGen = classGen;
|
||||
this.annotationGen = classGen.getAnnotationGen();
|
||||
this.nameGen = new NameGen(mth, classGen.isFallbackMode());
|
||||
}
|
||||
|
||||
public ClassGen getClassGen() {
|
||||
return classGen;
|
||||
}
|
||||
|
||||
public NameGen getNameGen() {
|
||||
return nameGen;
|
||||
}
|
||||
|
||||
public MethodNode getMethodNode() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public boolean addDefinition(CodeWriter code) {
|
||||
if (mth.getMethodInfo().isClassInit()) {
|
||||
code.attachDefinition(mth);
|
||||
code.startLine("static");
|
||||
return true;
|
||||
}
|
||||
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||
// don't add method name and arguments
|
||||
code.startLine();
|
||||
code.attachDefinition(mth);
|
||||
return false;
|
||||
}
|
||||
annotationGen.addForMethod(code, mth);
|
||||
|
||||
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
|
||||
AccessInfo ai = mth.getAccessFlags();
|
||||
// don't add 'abstract' and 'public' to methods in interface
|
||||
if (clsAccFlags.isInterface()) {
|
||||
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
}
|
||||
// don't add 'public' for annotations
|
||||
if (clsAccFlags.isAnnotation()) {
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
}
|
||||
|
||||
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
|
||||
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
|
||||
}
|
||||
CodeGenUtils.addSourceFileInfo(code, mth);
|
||||
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
|
||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||
}
|
||||
|
||||
code.startLineWithNum(mth.getSourceLine());
|
||||
code.add(ai.makeString());
|
||||
if (Consts.DEBUG) {
|
||||
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
|
||||
}
|
||||
|
||||
if (classGen.addGenericMap(code, mth.getGenericMap(), false)) {
|
||||
code.add(' ');
|
||||
}
|
||||
if (ai.isConstructor()) {
|
||||
code.attachDefinition(mth);
|
||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||
} else {
|
||||
classGen.useType(code, mth.getReturnType());
|
||||
code.add(' ');
|
||||
code.attachDefinition(mth);
|
||||
code.add(mth.getAlias());
|
||||
}
|
||||
code.add('(');
|
||||
|
||||
List<RegisterArg> args = mth.getArguments(false);
|
||||
if (mth.getMethodInfo().isConstructor()
|
||||
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
|
||||
if (args.size() == 2) {
|
||||
args.clear();
|
||||
} else if (args.size() > 2) {
|
||||
args = args.subList(2, args.size());
|
||||
} else {
|
||||
mth.addComment("JADX WARN: Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
|
||||
}
|
||||
} else if (mth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
args = args.subList(1, args.size());
|
||||
}
|
||||
addMethodArguments(code, args);
|
||||
code.add(')');
|
||||
|
||||
annotationGen.addThrows(mth, code);
|
||||
|
||||
// add default value if in annotation class
|
||||
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
|
||||
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||
if (def != null) {
|
||||
code.add(" default ");
|
||||
annotationGen.encodeValue(code, def);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) {
|
||||
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
||||
int i = 0;
|
||||
Iterator<RegisterArg> it = args.iterator();
|
||||
while (it.hasNext()) {
|
||||
RegisterArg mthArg = it.next();
|
||||
SSAVar ssaVar = mthArg.getSVar();
|
||||
CodeVar var;
|
||||
if (ssaVar == null) {
|
||||
// null for abstract or interface methods
|
||||
var = CodeVar.fromMthArg(mthArg);
|
||||
} else {
|
||||
var = ssaVar.getCodeVar();
|
||||
}
|
||||
|
||||
// add argument annotation
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(code, paramsAnnotation, i);
|
||||
}
|
||||
if (var.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
ArgType argType;
|
||||
if (var.getType() == ArgType.UNKNOWN) {
|
||||
// occur on decompilation errors
|
||||
argType = mthArg.getInitType();
|
||||
} else {
|
||||
argType = var.getType();
|
||||
}
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
if (argType.isArray()) {
|
||||
ArgType elType = argType.getArrayElement();
|
||||
classGen.useType(code, elType);
|
||||
code.add("...");
|
||||
} else {
|
||||
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var);
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
} else {
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
code.add(' ');
|
||||
code.add(nameGen.assignArg(var));
|
||||
|
||||
i++;
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addInstructions(CodeWriter code) throws CodegenException {
|
||||
if (mth.root().getArgs().isFallbackMode()) {
|
||||
addFallbackMethodCode(code);
|
||||
} else if (classGen.isFallbackMode()) {
|
||||
dumpInstructions(code);
|
||||
} else {
|
||||
addRegionInsns(code);
|
||||
}
|
||||
}
|
||||
|
||||
public void addRegionInsns(CodeWriter code) throws CodegenException {
|
||||
try {
|
||||
RegionGen regionGen = new RegionGen(this);
|
||||
regionGen.makeRegion(code, mth.getRegion());
|
||||
} catch (StackOverflowError | BootstrapMethodError e) {
|
||||
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
|
||||
classGen.insertDecompilationProblems(code, mth);
|
||||
dumpInstructions(code);
|
||||
} catch (Exception e) {
|
||||
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||
throw e;
|
||||
}
|
||||
mth.addError("Method code generation error", e);
|
||||
classGen.insertDecompilationProblems(code, mth);
|
||||
dumpInstructions(code);
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpInstructions(CodeWriter code) {
|
||||
code.startLine("/*");
|
||||
addFallbackMethodCode(code);
|
||||
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) {
|
||||
if (mth.getInstructions() == null) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
InsnNode[] insnArr = mth.getInstructions();
|
||||
if (insnArr == null) {
|
||||
code.startLine("// Can't load method instructions.");
|
||||
return;
|
||||
}
|
||||
code.incIndent();
|
||||
if (mth.getThisArg() != null) {
|
||||
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
||||
}
|
||||
addFallbackInsns(code, mth, insnArr, true);
|
||||
code.decIndent();
|
||||
}
|
||||
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
boolean attachInsns = mth.root().getArgs().isJsonOutput();
|
||||
InsnNode prevInsn = null;
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn == null) {
|
||||
continue;
|
||||
}
|
||||
if (addLabels && needLabel(insn, prevInsn)) {
|
||||
code.decIndent();
|
||||
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||
code.incIndent();
|
||||
}
|
||||
if (insn.getType() == InsnType.NOP) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
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(' ');
|
||||
}
|
||||
}
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
code.add(" // " + catchAttr);
|
||||
}
|
||||
} catch (CodegenException e) {
|
||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||
code.startLine("// error: " + insn);
|
||||
}
|
||||
prevInsn = insn;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needLabel(InsnNode insn, InsnNode prevInsn) {
|
||||
if (insn.contains(AType.EXC_HANDLER)) {
|
||||
return true;
|
||||
}
|
||||
if (insn.contains(AType.JUMP)) {
|
||||
// don't add label for ifs else branch
|
||||
if (prevInsn != null && prevInsn.getType() == InsnType.IF) {
|
||||
List<JumpInfo> jumps = insn.getAll(AType.JUMP);
|
||||
if (jumps.size() == 1) {
|
||||
JumpInfo jump = jumps.get(0);
|
||||
if (jump.getSrc() == prevInsn.getOffset() && jump.getDest() == insn.getOffset()) {
|
||||
int target = ((IfNode) prevInsn).getTarget();
|
||||
return insn.getOffset() == target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fallback variant of method codegen
|
||||
*/
|
||||
public static MethodGen getFallbackMethodGen(MethodNode mth) {
|
||||
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, false, true, true);
|
||||
return new MethodGen(clsGen, mth);
|
||||
}
|
||||
|
||||
public static String getLabelName(int offset) {
|
||||
return "L_" + InsnUtils.formatOffset(offset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class NameGen {
|
||||
|
||||
private static final Map<String, String> OBJ_ALIAS;
|
||||
|
||||
private final Set<String> varNames = new HashSet<>();
|
||||
private final MethodNode mth;
|
||||
private final boolean fallback;
|
||||
|
||||
static {
|
||||
OBJ_ALIAS = Utils.newConstStringMap(
|
||||
Consts.CLASS_STRING, "str",
|
||||
Consts.CLASS_CLASS, "cls",
|
||||
Consts.CLASS_THROWABLE, "th",
|
||||
Consts.CLASS_OBJECT, "obj",
|
||||
"java.util.Iterator", "it",
|
||||
"java.lang.Boolean", "bool",
|
||||
"java.lang.Short", "sh",
|
||||
"java.lang.Integer", "num",
|
||||
"java.lang.Character", "ch",
|
||||
"java.lang.Byte", "b",
|
||||
"java.lang.Float", "f",
|
||||
"java.lang.Long", "l",
|
||||
"java.lang.Double", "d",
|
||||
"java.lang.StringBuilder", "sb",
|
||||
"java.lang.Exception", "exc");
|
||||
}
|
||||
|
||||
public NameGen(MethodNode mth, boolean fallback) {
|
||||
this.mth = mth;
|
||||
this.fallback = fallback;
|
||||
addNamesUsedInClass();
|
||||
}
|
||||
|
||||
private void addNamesUsedInClass() {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
for (FieldNode field : parentClass.getFields()) {
|
||||
varNames.add(field.getAlias());
|
||||
}
|
||||
for (ClassNode innerClass : parentClass.getInnerClasses()) {
|
||||
varNames.add(innerClass.getClassInfo().getAliasShortName());
|
||||
}
|
||||
// add all root package names to avoid collisions with full class names
|
||||
varNames.addAll(mth.root().getCacheStorage().getRootPkgs());
|
||||
}
|
||||
|
||||
public String assignArg(CodeVar var) {
|
||||
String name = makeArgName(var);
|
||||
if (fallback) {
|
||||
return name;
|
||||
}
|
||||
name = getUniqueVarName(name);
|
||||
var.setName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
public String assignNamedArg(NamedArg arg) {
|
||||
String name = arg.getName();
|
||||
if (fallback) {
|
||||
return name;
|
||||
}
|
||||
name = getUniqueVarName(name);
|
||||
arg.setName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
public String useArg(RegisterArg arg) {
|
||||
String name = arg.getName();
|
||||
if (name == null || fallback) {
|
||||
return getFallbackName(arg);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
// TODO: avoid name collision with variables names
|
||||
public String getLoopLabel(LoopLabelAttr attr) {
|
||||
String name = "loop" + attr.getLoop().getId();
|
||||
varNames.add(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
private String getUniqueVarName(String name) {
|
||||
String r = name;
|
||||
int i = 2;
|
||||
while (varNames.contains(r)) {
|
||||
r = name + i;
|
||||
i++;
|
||||
}
|
||||
varNames.add(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
private String makeArgName(CodeVar var) {
|
||||
if (fallback) {
|
||||
return getFallbackName(var);
|
||||
}
|
||||
if (var.isThis()) {
|
||||
return RegisterArg.THIS_ARG_NAME;
|
||||
}
|
||||
String name = var.getName();
|
||||
String varName = name != null ? name : guessName(var);
|
||||
if (NameMapper.isReserved(varName)) {
|
||||
varName = varName + 'R';
|
||||
}
|
||||
if (!NameMapper.isValidAndPrintable(varName)) {
|
||||
varName = getFallbackName(var);
|
||||
}
|
||||
return varName;
|
||||
}
|
||||
|
||||
private String getFallbackName(CodeVar var) {
|
||||
return getFallbackName(var.getSsaVars().get(0).getAssign());
|
||||
}
|
||||
|
||||
private String getFallbackName(RegisterArg arg) {
|
||||
return "r" + arg.getRegNum();
|
||||
}
|
||||
|
||||
private String guessName(CodeVar var) {
|
||||
List<SSAVar> ssaVars = var.getSsaVars();
|
||||
if (ssaVars != null && !ssaVars.isEmpty()) {
|
||||
// TODO: use all vars for better name generation
|
||||
SSAVar ssaVar = ssaVars.get(0);
|
||||
if (ssaVar != null && ssaVar.getName() == null) {
|
||||
RegisterArg assignArg = ssaVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
if (assignInsn != null) {
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return makeNameForType(var.getType());
|
||||
}
|
||||
|
||||
private String makeNameForType(ArgType type) {
|
||||
if (type.isPrimitive()) {
|
||||
return makeNameForPrimitive(type);
|
||||
}
|
||||
if (type.isArray()) {
|
||||
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||
}
|
||||
return makeNameForObject(type);
|
||||
}
|
||||
|
||||
private static String makeNameForPrimitive(ArgType type) {
|
||||
return type.getPrimitiveType().getShortName().toLowerCase();
|
||||
}
|
||||
|
||||
private String makeNameForObject(ArgType type) {
|
||||
if (type.isGenericType()) {
|
||||
return StringUtils.escape(type.getObject().toLowerCase());
|
||||
}
|
||||
if (type.isObject()) {
|
||||
String alias = getAliasForObject(type.getObject());
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
return vName;
|
||||
}
|
||||
if (shortName != null) {
|
||||
return StringUtils.escape(shortName.toLowerCase());
|
||||
}
|
||||
}
|
||||
return StringUtils.escape(type.toString());
|
||||
}
|
||||
|
||||
private static String fromName(String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (name.toUpperCase().equals(name)) {
|
||||
// all characters are upper case
|
||||
return name.toLowerCase();
|
||||
}
|
||||
String v1 = Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||
if (!v1.equals(name)) {
|
||||
return v1;
|
||||
}
|
||||
if (name.length() < 3) {
|
||||
return name + "Var";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getAliasForObject(String name) {
|
||||
return OBJ_ALIAS.get(name);
|
||||
}
|
||||
|
||||
private String makeNameFromInsn(InsnNode insn) {
|
||||
switch (insn.getType()) {
|
||||
case INVOKE:
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
return makeNameFromInvoke(inv.getCallMth());
|
||||
|
||||
case CONSTRUCTOR:
|
||||
ConstructorInsn co = (ConstructorInsn) insn;
|
||||
return makeNameForObject(co.getClassType().getType());
|
||||
|
||||
case ARRAY_LENGTH:
|
||||
return "length";
|
||||
|
||||
case ARITH:
|
||||
case TERNARY:
|
||||
case CAST:
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
String wName = makeNameFromInsn(wrapInsn);
|
||||
if (wName != null) {
|
||||
return wName;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String makeNameFromInvoke(MethodInfo callMth) {
|
||||
String name = callMth.getName();
|
||||
if (name.startsWith("get") || name.startsWith("set")) {
|
||||
return fromName(name.substring(3));
|
||||
}
|
||||
if ("iterator".equals(name)) {
|
||||
return "it";
|
||||
}
|
||||
ArgType declType = callMth.getDeclClass().getType();
|
||||
if ("toString".equals(name)) {
|
||||
return makeNameForType(declType);
|
||||
}
|
||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||
}
|
||||
if (name.startsWith("to")) {
|
||||
return fromName(name.substring(2));
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SynchronizedRegion;
|
||||
import jadx.core.dex.regions.TryCatchRegion;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfRegion;
|
||||
import jadx.core.dex.regions.loops.ForEachLoop;
|
||||
import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class RegionGen extends InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
|
||||
|
||||
public RegionGen(MethodGen mgen) {
|
||||
super(mgen, false);
|
||||
}
|
||||
|
||||
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException {
|
||||
if (cont instanceof IBlock) {
|
||||
makeSimpleBlock((IBlock) cont, code);
|
||||
} else if (cont instanceof IRegion) {
|
||||
if (cont instanceof Region) {
|
||||
makeSimpleRegion(code, (Region) cont);
|
||||
} else {
|
||||
declareVars(code, cont);
|
||||
if (cont instanceof IfRegion) {
|
||||
makeIf((IfRegion) cont, code, true);
|
||||
} else if (cont instanceof SwitchRegion) {
|
||||
makeSwitch((SwitchRegion) cont, code);
|
||||
} else if (cont instanceof LoopRegion) {
|
||||
makeLoop((LoopRegion) cont, code);
|
||||
} else if (cont instanceof TryCatchRegion) {
|
||||
makeTryCatch((TryCatchRegion) cont, code);
|
||||
} else if (cont instanceof SynchronizedRegion) {
|
||||
makeSynchronizedRegion((SynchronizedRegion) cont, code);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new CodegenException("Not processed container: " + cont);
|
||||
}
|
||||
}
|
||||
|
||||
private void declareVars(CodeWriter code, IContainer cont) {
|
||||
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
||||
if (declVars != null) {
|
||||
for (CodeVar v : declVars.getVars()) {
|
||||
code.startLine();
|
||||
declareVar(code, v);
|
||||
code.add(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
|
||||
declareVars(code, region);
|
||||
for (IContainer c : region.getSubBlocks()) {
|
||||
makeRegion(code, c);
|
||||
}
|
||||
}
|
||||
|
||||
public void makeRegionIndent(CodeWriter code, IContainer region) throws CodegenException {
|
||||
code.incIndent();
|
||||
makeRegion(code, region);
|
||||
code.decIndent();
|
||||
}
|
||||
|
||||
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (!insn.contains(AFlag.DONT_GENERATE)) {
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
|
||||
if (retAttr != null) {
|
||||
makeInsn(retAttr.getReturnInsn(), code);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
|
||||
if (newLine) {
|
||||
code.startLineWithNum(region.getSourceLine());
|
||||
} else {
|
||||
code.attachSourceLine(region.getSourceLine());
|
||||
}
|
||||
if (attachInsns) {
|
||||
List<BlockNode> conditionBlocks = region.getConditionBlocks();
|
||||
if (!conditionBlocks.isEmpty()) {
|
||||
BlockNode blockNode = conditionBlocks.get(0);
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||
if (lastInsn != null) {
|
||||
code.attachLineAnnotation(lastInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code.add("if (");
|
||||
new ConditionGen(this).add(code, region.getCondition());
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getThenRegion());
|
||||
code.startLine('}');
|
||||
|
||||
IContainer els = region.getElseRegion();
|
||||
if (RegionUtils.notEmpty(els)) {
|
||||
code.add(" else ");
|
||||
if (connectElseIf(code, els)) {
|
||||
return;
|
||||
}
|
||||
code.add('{');
|
||||
makeRegionIndent(code, els);
|
||||
code.startLine('}');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect if-else-if block
|
||||
*/
|
||||
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
|
||||
if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) {
|
||||
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
||||
if (subBlocks.size() == 1) {
|
||||
IContainer elseBlock = subBlocks.get(0);
|
||||
if (elseBlock instanceof IfRegion) {
|
||||
declareVars(code, elseBlock);
|
||||
makeIf((IfRegion) elseBlock, code, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
|
||||
BlockNode header = region.getHeader();
|
||||
if (header != null) {
|
||||
List<InsnNode> headerInsns = header.getInstructions();
|
||||
if (headerInsns.size() > 1) {
|
||||
ErrorsCounter.methodWarn(mth, "Found not inlined instructions from loop header");
|
||||
int last = headerInsns.size() - 1;
|
||||
for (int i = 0; i < last; i++) {
|
||||
InsnNode insn = headerInsns.get(i);
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||
if (labelAttr != null) {
|
||||
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
||||
}
|
||||
|
||||
IfCondition condition = region.getCondition();
|
||||
if (condition == null) {
|
||||
// infinite loop
|
||||
code.startLine("while (true) {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
ConditionGen conditionGen = new ConditionGen(this);
|
||||
LoopType type = region.getType();
|
||||
if (type != null) {
|
||||
if (type instanceof ForLoop) {
|
||||
ForLoop forLoop = (ForLoop) type;
|
||||
code.startLine("for (");
|
||||
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
|
||||
code.add("; ");
|
||||
conditionGen.add(code, condition);
|
||||
code.add("; ");
|
||||
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
if (type instanceof ForEachLoop) {
|
||||
ForEachLoop forEachLoop = (ForEachLoop) type;
|
||||
code.startLine("for (");
|
||||
declareVar(code, forEachLoop.getVarArg());
|
||||
code.add(" : ");
|
||||
addArg(code, forEachLoop.getIterableArg(), false);
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
|
||||
}
|
||||
if (region.isConditionAtEnd()) {
|
||||
code.startLine("do {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("} while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(");");
|
||||
} else {
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException {
|
||||
code.startLine("synchronized (");
|
||||
addArg(code, cont.getEnterInsn().getArg(0));
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, cont.getRegion());
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
|
||||
SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader());
|
||||
Objects.requireNonNull(insn, "Switch insn not found in header");
|
||||
InsnArg arg = insn.getArg(0);
|
||||
code.startLine("switch (");
|
||||
addArg(code, arg, false);
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
|
||||
int size = sw.getKeys().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
List<Object> keys = sw.getKeys().get(i);
|
||||
IContainer c = sw.getCases().get(i);
|
||||
for (Object k : keys) {
|
||||
code.startLine("case ");
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
code.add(fn.getAlias());
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replace with incorrect field
|
||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
||||
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(), mth, fallback));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
code.add(':');
|
||||
}
|
||||
makeRegionIndent(code, c);
|
||||
}
|
||||
if (sw.getDefaultCase() != null) {
|
||||
code.startLine("default:");
|
||||
makeRegionIndent(code, sw.getDefaultCase());
|
||||
}
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
|
||||
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
|
||||
code.startLine("try {");
|
||||
makeRegionIndent(code, region.getTryRegion());
|
||||
// TODO: move search of 'allHandler' to 'TryCatchRegion'
|
||||
ExceptionHandler allHandler = null;
|
||||
for (Map.Entry<ExceptionHandler, IContainer> entry : region.getCatchRegions().entrySet()) {
|
||||
ExceptionHandler handler = entry.getKey();
|
||||
if (handler.isCatchAll()) {
|
||||
if (allHandler != null) {
|
||||
LOG.warn("Several 'all' handlers in try/catch block in {}", mth);
|
||||
}
|
||||
allHandler = handler;
|
||||
} else {
|
||||
makeCatchBlock(code, handler);
|
||||
}
|
||||
}
|
||||
if (allHandler != null) {
|
||||
makeCatchBlock(code, allHandler);
|
||||
}
|
||||
IContainer finallyRegion = region.getFinallyRegion();
|
||||
if (finallyRegion != null) {
|
||||
code.startLine("} finally {");
|
||||
makeRegionIndent(code, finallyRegion);
|
||||
}
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
|
||||
IContainer region = handler.getHandlerRegion();
|
||||
if (region == null) {
|
||||
return;
|
||||
}
|
||||
code.startLine("} catch (");
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
} else {
|
||||
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
|
||||
if (it.hasNext()) {
|
||||
useClass(code, it.next());
|
||||
}
|
||||
while (it.hasNext()) {
|
||||
code.add(" | ");
|
||||
useClass(code, it.next());
|
||||
}
|
||||
}
|
||||
code.add(' ');
|
||||
InsnArg arg = handler.getArg();
|
||||
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(") {");
|
||||
makeRegionIndent(code, region);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.IDexNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class TypeGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
|
||||
|
||||
private TypeGen() {
|
||||
}
|
||||
|
||||
public static String signature(ArgType type) {
|
||||
PrimitiveType stype = type.getPrimitiveType();
|
||||
if (stype == PrimitiveType.OBJECT) {
|
||||
return Utils.makeQualifiedObjectName(type.getObject());
|
||||
}
|
||||
if (stype == PrimitiveType.ARRAY) {
|
||||
return '[' + signature(type.getArrayElement());
|
||||
}
|
||||
return stype.getShortName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert literal value to string according to value type
|
||||
*
|
||||
* @throws JadxRuntimeException for incorrect type or literal value
|
||||
*/
|
||||
public static String literalToString(long lit, ArgType type, IDexNode dexNode, boolean fallback) {
|
||||
return literalToString(lit, type, dexNode.root().getStringUtils(), fallback);
|
||||
}
|
||||
|
||||
public static String literalToString(long lit, ArgType type, StringUtils stringUtils, boolean fallback) {
|
||||
if (type == null || !type.isTypeKnown()) {
|
||||
String n = Long.toString(lit);
|
||||
if (fallback && Math.abs(lit) > 100) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(n).append("(0x").append(Long.toHexString(lit));
|
||||
if (type == null || type.contains(PrimitiveType.FLOAT)) {
|
||||
sb.append(", float:").append(Float.intBitsToFloat((int) lit));
|
||||
}
|
||||
if (type == null || type.contains(PrimitiveType.DOUBLE)) {
|
||||
sb.append(", double:").append(Double.longBitsToDouble(lit));
|
||||
}
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
switch (type.getPrimitiveType()) {
|
||||
case BOOLEAN:
|
||||
return lit == 0 ? "false" : "true";
|
||||
case CHAR:
|
||||
char ch = (char) lit;
|
||||
if (!NameMapper.isPrintableChar(ch)) {
|
||||
return Integer.toString(ch);
|
||||
}
|
||||
return stringUtils.unescapeChar(ch);
|
||||
case BYTE:
|
||||
return formatByte(lit);
|
||||
case SHORT:
|
||||
return formatShort(lit);
|
||||
case INT:
|
||||
return formatInteger(lit);
|
||||
case LONG:
|
||||
return formatLong(lit);
|
||||
case FLOAT:
|
||||
return formatFloat(Float.intBitsToFloat((int) lit));
|
||||
case DOUBLE:
|
||||
return formatDouble(Double.longBitsToDouble(lit));
|
||||
|
||||
case OBJECT:
|
||||
case ARRAY:
|
||||
if (lit != 0) {
|
||||
LOG.warn("Wrong object literal: {} for type: {}", lit, type);
|
||||
return Long.toString(lit);
|
||||
}
|
||||
return "null";
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown type in literalToString: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatShort(long l) {
|
||||
if (l == Short.MAX_VALUE) {
|
||||
return "Short.MAX_VALUE";
|
||||
}
|
||||
if (l == Short.MIN_VALUE) {
|
||||
return "Short.MIN_VALUE";
|
||||
}
|
||||
return Long.toString(l);
|
||||
}
|
||||
|
||||
public static String formatByte(long l) {
|
||||
if (l == Byte.MAX_VALUE) {
|
||||
return "Byte.MAX_VALUE";
|
||||
}
|
||||
if (l == Byte.MIN_VALUE) {
|
||||
return "Byte.MIN_VALUE";
|
||||
}
|
||||
return Long.toString(l);
|
||||
}
|
||||
|
||||
public static String formatInteger(long l) {
|
||||
if (l == Integer.MAX_VALUE) {
|
||||
return "Integer.MAX_VALUE";
|
||||
}
|
||||
if (l == Integer.MIN_VALUE) {
|
||||
return "Integer.MIN_VALUE";
|
||||
}
|
||||
return Long.toString(l);
|
||||
}
|
||||
|
||||
public static String formatLong(long l) {
|
||||
if (l == Long.MAX_VALUE) {
|
||||
return "Long.MAX_VALUE";
|
||||
}
|
||||
if (l == Long.MIN_VALUE) {
|
||||
return "Long.MIN_VALUE";
|
||||
}
|
||||
String str = Long.toString(l);
|
||||
if (Math.abs(l) >= Integer.MAX_VALUE) {
|
||||
str += 'L';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static String formatDouble(double d) {
|
||||
if (Double.isNaN(d)) {
|
||||
return "Double.NaN";
|
||||
}
|
||||
if (d == Double.NEGATIVE_INFINITY) {
|
||||
return "Double.NEGATIVE_INFINITY";
|
||||
}
|
||||
if (d == Double.POSITIVE_INFINITY) {
|
||||
return "Double.POSITIVE_INFINITY";
|
||||
}
|
||||
if (d == Double.MIN_VALUE) {
|
||||
return "Double.MIN_VALUE";
|
||||
}
|
||||
if (d == Double.MAX_VALUE) {
|
||||
return "Double.MAX_VALUE";
|
||||
}
|
||||
if (d == Double.MIN_NORMAL) {
|
||||
return "Double.MIN_NORMAL";
|
||||
}
|
||||
return Double.toString(d) + 'd';
|
||||
}
|
||||
|
||||
public static String formatFloat(float f) {
|
||||
if (Float.isNaN(f)) {
|
||||
return "Float.NaN";
|
||||
}
|
||||
if (f == Float.NEGATIVE_INFINITY) {
|
||||
return "Float.NEGATIVE_INFINITY";
|
||||
}
|
||||
if (f == Float.POSITIVE_INFINITY) {
|
||||
return "Float.POSITIVE_INFINITY";
|
||||
}
|
||||
if (f == Float.MIN_VALUE) {
|
||||
return "Float.MIN_VALUE";
|
||||
}
|
||||
if (f == Float.MAX_VALUE) {
|
||||
return "Float.MAX_VALUE";
|
||||
}
|
||||
if (f == Float.MIN_NORMAL) {
|
||||
return "Float.MIN_NORMAL";
|
||||
}
|
||||
return Float.toString(f) + 'f';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
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.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.dex().getDexFile().getName());
|
||||
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.finish().toString());
|
||||
|
||||
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.finish().toString());
|
||||
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.finish().toString());
|
||||
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 code = new CodeWriter();
|
||||
try {
|
||||
mthGen.addInstructions(code);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Method generation error", e);
|
||||
}
|
||||
code.finish();
|
||||
String codeStr = code.toString();
|
||||
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) {
|
||||
int 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
class DeobfClsInfo {
|
||||
private final Deobfuscator deobfuscator;
|
||||
private final ClassNode cls;
|
||||
private final PackageNode pkg;
|
||||
private final String alias;
|
||||
|
||||
public DeobfClsInfo(Deobfuscator deobfuscator, ClassNode cls, PackageNode pkg, String alias) {
|
||||
this.deobfuscator = deobfuscator;
|
||||
this.cls = cls;
|
||||
this.pkg = pkg;
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String makeNameWithoutPkg() {
|
||||
String prefix;
|
||||
ClassNode parentClass = cls.getParentClass();
|
||||
if (parentClass != cls) {
|
||||
DeobfClsInfo parentDeobfClsInfo = deobfuscator.getClsMap().get(parentClass.getClassInfo());
|
||||
if (parentDeobfClsInfo != null) {
|
||||
prefix = parentDeobfClsInfo.makeNameWithoutPkg();
|
||||
} else {
|
||||
prefix = deobfuscator.getNameWithoutPackage(parentClass.getClassInfo());
|
||||
}
|
||||
prefix += Deobfuscator.INNER_CLASS_SEPARATOR;
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
return prefix + (this.alias != null ? this.alias : this.cls.getShortName());
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return pkg.getFullAlias() + Deobfuscator.CLASS_NAME_SEPARATOR + makeNameWithoutPkg();
|
||||
}
|
||||
|
||||
public ClassNode getCls() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public PackageNode getPkg() {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
class DeobfPresets {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
|
||||
|
||||
private static final Charset MAP_FILE_CHARSET = UTF_8;
|
||||
|
||||
private final Deobfuscator deobfuscator;
|
||||
private final Path deobfMapFile;
|
||||
|
||||
private final Map<String, String> clsPresetMap = new HashMap<>();
|
||||
private final Map<String, String> fldPresetMap = new HashMap<>();
|
||||
private final Map<String, String> mthPresetMap = new HashMap<>();
|
||||
|
||||
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) {
|
||||
this.deobfuscator = deobfuscator;
|
||||
this.deobfMapFile = deobfMapFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads deobfuscator presets
|
||||
*/
|
||||
public void load() {
|
||||
if (!Files.exists(deobfMapFile)) {
|
||||
return;
|
||||
}
|
||||
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(deobfMapFile, MAP_FILE_CHARSET);
|
||||
for (String l : lines) {
|
||||
l = l.trim();
|
||||
if (l.isEmpty() || l.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
String[] va = splitAndTrim(l);
|
||||
if (va.length != 2) {
|
||||
continue;
|
||||
}
|
||||
String origName = va[0];
|
||||
String alias = va[1];
|
||||
if (l.startsWith("p ")) {
|
||||
deobfuscator.addPackagePreset(origName, alias);
|
||||
} else if (l.startsWith("c ")) {
|
||||
clsPresetMap.put(origName, alias);
|
||||
} else if (l.startsWith("f ")) {
|
||||
fldPresetMap.put(origName, alias);
|
||||
} else if (l.startsWith("m ")) {
|
||||
mthPresetMap.put(origName, alias);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] splitAndTrim(String str) {
|
||||
String[] v = str.substring(2).split("=");
|
||||
for (int i = 0; i < v.length; i++) {
|
||||
v[i] = v[i].trim();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public void save(boolean forceSave) {
|
||||
try {
|
||||
if (Files.exists(deobfMapFile)) {
|
||||
if (forceSave) {
|
||||
dumpMapping();
|
||||
} else {
|
||||
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||
deobfMapFile.toAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
dumpMapping();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves DefaultDeobfuscator presets
|
||||
*/
|
||||
private void dumpMapping() throws IOException {
|
||||
List<String> list = new ArrayList<>();
|
||||
// packages
|
||||
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
|
||||
for (PackageNode pp : p.getInnerPackages()) {
|
||||
dfsPackageName(list, p.getName(), pp);
|
||||
}
|
||||
if (p.hasAlias()) {
|
||||
list.add(String.format("p %s = %s", p.getName(), p.getAlias()));
|
||||
}
|
||||
}
|
||||
// classes
|
||||
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
|
||||
if (deobfClsInfo.getAlias() != null) {
|
||||
list.add(String.format("c %s = %s",
|
||||
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
|
||||
}
|
||||
}
|
||||
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
|
||||
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
|
||||
}
|
||||
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
|
||||
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
|
||||
}
|
||||
Collections.sort(list);
|
||||
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
|
||||
for (PackageNode pp : node.getInnerPackages()) {
|
||||
dfsPackageName(list, prefix + '.' + node.getName(), pp);
|
||||
}
|
||||
if (node.hasAlias()) {
|
||||
list.add(String.format("p %s.%s = %s", prefix, node.getName(), node.getAlias()));
|
||||
}
|
||||
}
|
||||
|
||||
public String getForCls(ClassInfo cls) {
|
||||
return clsPresetMap.get(cls.makeRawFullName());
|
||||
}
|
||||
|
||||
public String getForFld(FieldInfo fld) {
|
||||
return fldPresetMap.get(fld.getRawFullId());
|
||||
}
|
||||
|
||||
public String getForMth(MethodInfo mth) {
|
||||
return mthPresetMap.get(mth.getRawFullId());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
clsPresetMap.clear();
|
||||
fldPresetMap.clear();
|
||||
mthPresetMap.clear();
|
||||
}
|
||||
|
||||
public Map<String, String> getClsPresetMap() {
|
||||
return clsPresetMap;
|
||||
}
|
||||
|
||||
public Map<String, String> getFldPresetMap() {
|
||||
return fldPresetMap;
|
||||
}
|
||||
|
||||
public Map<String, String> getMthPresetMap() {
|
||||
return mthPresetMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class Deobfuscator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public static final String CLASS_NAME_SEPARATOR = ".";
|
||||
public static final String INNER_CLASS_SEPARATOR = "$";
|
||||
|
||||
private final JadxArgs args;
|
||||
@NotNull
|
||||
private final List<DexNode> dexNodes;
|
||||
private final DeobfPresets deobfPresets;
|
||||
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
|
||||
private final Map<FieldInfo, String> fldMap = new HashMap<>();
|
||||
private final Map<MethodInfo, String> mthMap = new HashMap<>();
|
||||
|
||||
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<>();
|
||||
private final List<OverridedMethodsNode> ovrd = new ArrayList<>();
|
||||
|
||||
private final PackageNode rootPackage = new PackageNode("");
|
||||
private final Set<String> pkgSet = new TreeSet<>();
|
||||
private final Set<String> reservedClsNames = new HashSet<>();
|
||||
|
||||
private final int maxLength;
|
||||
private final int minLength;
|
||||
private final boolean useSourceNameAsAlias;
|
||||
|
||||
private int pkgIndex = 0;
|
||||
private int clsIndex = 0;
|
||||
private int fldIndex = 0;
|
||||
private int mthIndex = 0;
|
||||
|
||||
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, Path deobfMapFile) {
|
||||
this.args = args;
|
||||
this.dexNodes = dexNodes;
|
||||
|
||||
this.minLength = args.getDeobfuscationMinLength();
|
||||
this.maxLength = args.getDeobfuscationMaxLength();
|
||||
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
|
||||
|
||||
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
if (!args.isDeobfuscationForceSave()) {
|
||||
deobfPresets.load();
|
||||
initIndexes();
|
||||
}
|
||||
process();
|
||||
}
|
||||
|
||||
public void savePresets() {
|
||||
deobfPresets.save(args.isDeobfuscationForceSave());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
deobfPresets.clear();
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
private void initIndexes() {
|
||||
pkgIndex = pkgSet.size();
|
||||
clsIndex = deobfPresets.getClsPresetMap().size();
|
||||
fldIndex = deobfPresets.getFldPresetMap().size();
|
||||
mthIndex = deobfPresets.getMthPresetMap().size();
|
||||
}
|
||||
|
||||
private void preProcess() {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
|
||||
}
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
preProcessClass(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void process() {
|
||||
preProcess();
|
||||
if (DEBUG) {
|
||||
dumpAlias();
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
processClass(cls);
|
||||
}
|
||||
}
|
||||
postProcess();
|
||||
}
|
||||
|
||||
private void postProcess() {
|
||||
int id = 1;
|
||||
for (OverridedMethodsNode o : ovrd) {
|
||||
boolean aliasFromPreset = false;
|
||||
String aliasToUse = null;
|
||||
for (MethodInfo mth : o.getMethods()) {
|
||||
if (mth.isAliasFromPreset()) {
|
||||
aliasToUse = mth.getAlias();
|
||||
aliasFromPreset = true;
|
||||
}
|
||||
}
|
||||
for (MethodInfo mth : o.getMethods()) {
|
||||
if (aliasToUse == null) {
|
||||
if (mth.hasAlias() && !mth.isAliasFromPreset()) {
|
||||
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
|
||||
}
|
||||
aliasToUse = mth.getAlias();
|
||||
}
|
||||
mth.setAlias(aliasToUse);
|
||||
mth.setAliasFromPreset(aliasFromPreset);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveOverriding(MethodNode mth) {
|
||||
Set<ClassNode> clsParents = new LinkedHashSet<>();
|
||||
collectClassHierarchy(mth.getParentClass(), clsParents);
|
||||
|
||||
String mthSignature = mth.getMethodInfo().makeSignature(false);
|
||||
Set<MethodInfo> overrideSet = new LinkedHashSet<>();
|
||||
for (ClassNode classNode : clsParents) {
|
||||
MethodInfo methodInfo = getMthOverride(classNode.getMethods(), mthSignature);
|
||||
if (methodInfo != null) {
|
||||
overrideSet.add(methodInfo);
|
||||
}
|
||||
}
|
||||
if (overrideSet.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
OverridedMethodsNode overrideNode = getOverrideMethodsNode(overrideSet);
|
||||
if (overrideNode == null) {
|
||||
overrideNode = new OverridedMethodsNode(overrideSet);
|
||||
ovrd.add(overrideNode);
|
||||
}
|
||||
for (MethodInfo overrideMth : overrideSet) {
|
||||
if (!ovrdMap.containsKey(overrideMth)) {
|
||||
ovrdMap.put(overrideMth, overrideNode);
|
||||
overrideNode.add(overrideMth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OverridedMethodsNode getOverrideMethodsNode(Set<MethodInfo> overrideSet) {
|
||||
for (MethodInfo overrideMth : overrideSet) {
|
||||
OverridedMethodsNode node = ovrdMap.get(overrideMth);
|
||||
if (node != null) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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.dex().resolveClass(superClass);
|
||||
if (superNode != null) {
|
||||
collectClassHierarchy(superNode, collected);
|
||||
}
|
||||
}
|
||||
|
||||
for (ArgType argType : cls.getInterfaces()) {
|
||||
ClassNode interfaceNode = cls.dex().resolveClass(argType);
|
||||
if (interfaceNode != null) {
|
||||
collectClassHierarchy(interfaceNode, collected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(ClassNode cls) {
|
||||
if (isR(cls.getParentClass())) {
|
||||
return;
|
||||
}
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
clsInfo.changeShortName(deobfClsInfo.getAlias());
|
||||
PackageNode pkgNode = deobfClsInfo.getPkg();
|
||||
if (!clsInfo.isInner() && pkgNode.hasAnyAlias()) {
|
||||
clsInfo.changePkg(pkgNode.getFullAlias());
|
||||
}
|
||||
} else if (!clsInfo.isInner()) {
|
||||
// check if package renamed
|
||||
PackageNode pkgNode = getPackageNode(clsInfo.getPackage(), false);
|
||||
if (pkgNode != null && pkgNode.hasAnyAlias()) {
|
||||
clsInfo.changePkg(pkgNode.getFullAlias());
|
||||
}
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.contains(AFlag.DONT_RENAME)) {
|
||||
continue;
|
||||
}
|
||||
renameField(field);
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
renameMethod(mth);
|
||||
}
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
processClass(innerCls);
|
||||
}
|
||||
}
|
||||
|
||||
private void renameField(FieldNode field) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = getFieldAlias(field);
|
||||
if (alias != null) {
|
||||
fieldInfo.setAlias(alias);
|
||||
}
|
||||
}
|
||||
|
||||
public void forceRenameField(FieldNode field) {
|
||||
field.getFieldInfo().setAlias(makeFieldAlias(field));
|
||||
}
|
||||
|
||||
private void renameMethod(MethodNode mth) {
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
mth.getMethodInfo().setAlias(alias);
|
||||
}
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(mth);
|
||||
}
|
||||
}
|
||||
|
||||
public void forceRenameMethod(MethodNode mth) {
|
||||
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(mth);
|
||||
}
|
||||
}
|
||||
|
||||
public void addPackagePreset(String origPkgName, String pkgAlias) {
|
||||
PackageNode pkg = getPackageNode(origPkgName, true);
|
||||
pkg.setAlias(pkgAlias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets package node for full package name
|
||||
*
|
||||
* @param fullPkgName full package name
|
||||
* @param create if {@code true} then will create all absent objects
|
||||
* @return package node object or {@code null} if no package found and <b>create</b> set to
|
||||
* {@code false}
|
||||
*/
|
||||
private PackageNode getPackageNode(String fullPkgName, boolean create) {
|
||||
if (fullPkgName.isEmpty() || fullPkgName.equals(CLASS_NAME_SEPARATOR)) {
|
||||
return rootPackage;
|
||||
}
|
||||
PackageNode result = rootPackage;
|
||||
PackageNode parentNode;
|
||||
do {
|
||||
String pkgName;
|
||||
int idx = fullPkgName.indexOf(CLASS_NAME_SEPARATOR);
|
||||
|
||||
if (idx > -1) {
|
||||
pkgName = fullPkgName.substring(0, idx);
|
||||
fullPkgName = fullPkgName.substring(idx + 1);
|
||||
} else {
|
||||
pkgName = fullPkgName;
|
||||
fullPkgName = "";
|
||||
}
|
||||
parentNode = result;
|
||||
result = result.getInnerPackageByName(pkgName);
|
||||
if (result == null && create) {
|
||||
result = new PackageNode(pkgName);
|
||||
parentNode.addInnerPackage(result);
|
||||
}
|
||||
} while (!fullPkgName.isEmpty() && result != null);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
String getNameWithoutPackage(ClassInfo clsInfo) {
|
||||
String prefix;
|
||||
ClassInfo parentClsInfo = clsInfo.getParentClass();
|
||||
if (parentClsInfo != null) {
|
||||
DeobfClsInfo parentDeobfClsInfo = clsMap.get(parentClsInfo);
|
||||
if (parentDeobfClsInfo != null) {
|
||||
prefix = parentDeobfClsInfo.makeNameWithoutPkg();
|
||||
} else {
|
||||
prefix = getNameWithoutPackage(parentClsInfo);
|
||||
}
|
||||
prefix += INNER_CLASS_SEPARATOR;
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
return prefix + clsInfo.getShortName();
|
||||
}
|
||||
|
||||
private void preProcessClass(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String pkgFullName = classInfo.getPackage();
|
||||
PackageNode pkg = getPackageNode(pkgFullName, true);
|
||||
doPkg(pkg, pkgFullName);
|
||||
|
||||
String alias = deobfPresets.getForCls(classInfo);
|
||||
if (alias != null) {
|
||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||
} else {
|
||||
if (!clsMap.containsKey(classInfo)) {
|
||||
String clsShortName = classInfo.getShortName();
|
||||
if (shouldRename(clsShortName) || reservedClsNames.contains(clsShortName)) {
|
||||
makeClsAlias(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
preProcessClass(innerCls);
|
||||
}
|
||||
}
|
||||
|
||||
public String getClsAlias(ClassNode cls) {
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(cls.getClassInfo());
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getAlias();
|
||||
}
|
||||
return makeClsAlias(cls);
|
||||
}
|
||||
|
||||
private String makeClsAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String alias = null;
|
||||
|
||||
if (this.useSourceNameAsAlias) {
|
||||
alias = getAliasFromSourceFile(cls);
|
||||
}
|
||||
|
||||
if (alias == null) {
|
||||
String clsName = classInfo.getShortName();
|
||||
alias = String.format("C%04d%s", clsIndex++, prepareNamePart(clsName));
|
||||
}
|
||||
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
|
||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||
return alias;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getAliasFromSourceFile(ClassNode cls) {
|
||||
SourceFileAttr sourceFileAttr = cls.get(AType.SOURCE_FILE);
|
||||
if (sourceFileAttr == null) {
|
||||
return null;
|
||||
}
|
||||
if (cls.getClassInfo().isInner()) {
|
||||
return null;
|
||||
}
|
||||
String name = sourceFileAttr.getFileName();
|
||||
if (name.endsWith(".java")) {
|
||||
name = name.substring(0, name.length() - ".java".length());
|
||||
} else if (name.endsWith(".kt")) {
|
||||
name = name.substring(0, name.length() - ".kt".length());
|
||||
}
|
||||
if (!NameMapper.isValidAndPrintable(name)) {
|
||||
return null;
|
||||
}
|
||||
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
||||
if (deobfClsInfo.getAlias().equals(name)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name);
|
||||
if (otherCls != null) {
|
||||
return null;
|
||||
}
|
||||
cls.remove(AType.SOURCE_FILE);
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getFieldAlias(FieldNode field) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = fldMap.get(fieldInfo);
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
alias = deobfPresets.getForFld(fieldInfo);
|
||||
if (alias != null) {
|
||||
fldMap.put(fieldInfo, alias);
|
||||
return alias;
|
||||
}
|
||||
if (shouldRename(field.getName())) {
|
||||
return makeFieldAlias(field);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getMethodAlias(MethodNode mth) {
|
||||
MethodInfo methodInfo = mth.getMethodInfo();
|
||||
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
|
||||
return null;
|
||||
}
|
||||
String alias = mthMap.get(methodInfo);
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
alias = deobfPresets.getForMth(methodInfo);
|
||||
if (alias != null) {
|
||||
mthMap.put(methodInfo, alias);
|
||||
methodInfo.setAliasFromPreset(true);
|
||||
return alias;
|
||||
}
|
||||
if (shouldRename(mth.getName())) {
|
||||
return makeMethodAlias(mth);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String makeFieldAlias(FieldNode field) {
|
||||
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName()));
|
||||
fldMap.put(field.getFieldInfo(), alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
public String makeMethodAlias(MethodNode mth) {
|
||||
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
|
||||
mthMap.put(mth.getMethodInfo(), alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
private void doPkg(PackageNode pkg, String fullName) {
|
||||
if (pkgSet.contains(fullName)) {
|
||||
return;
|
||||
}
|
||||
pkgSet.add(fullName);
|
||||
|
||||
// doPkg for all parent packages except root that not hasAliases
|
||||
PackageNode parentPkg = pkg.getParentPackage();
|
||||
while (!parentPkg.getName().isEmpty()) {
|
||||
if (!parentPkg.hasAlias()) {
|
||||
doPkg(parentPkg, parentPkg.getFullName());
|
||||
}
|
||||
parentPkg = parentPkg.getParentPackage();
|
||||
}
|
||||
|
||||
String pkgName = pkg.getName();
|
||||
if (!pkg.hasAlias() && shouldRename(pkgName)) {
|
||||
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkgName));
|
||||
pkg.setAlias(pkgAlias);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRename(String s) {
|
||||
int len = s.length();
|
||||
return len < minLength || len > maxLength;
|
||||
}
|
||||
|
||||
private String prepareNamePart(String name) {
|
||||
if (name.length() > maxLength) {
|
||||
return 'x' + Integer.toHexString(name.hashCode());
|
||||
}
|
||||
return NameMapper.removeInvalidCharsMiddle(name);
|
||||
}
|
||||
|
||||
private void dumpClassAlias(ClassNode cls) {
|
||||
PackageNode pkg = getPackageNode(cls.getPackage(), false);
|
||||
|
||||
if (pkg != null) {
|
||||
if (!cls.getFullName().equals(getClassFullName(cls))) {
|
||||
LOG.info("Alias name for class '{}' is '{}'", cls.getFullName(), getClassFullName(cls));
|
||||
}
|
||||
} else {
|
||||
LOG.error("Can't find package node for '{}'", cls.getPackage());
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpAlias() {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
dumpClassAlias(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getPackageName(String packageName) {
|
||||
PackageNode pkg = getPackageNode(packageName, false);
|
||||
if (pkg != null) {
|
||||
return pkg.getFullAlias();
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
|
||||
private String getClassName(ClassInfo clsInfo) {
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.makeNameWithoutPkg();
|
||||
}
|
||||
return getNameWithoutPackage(clsInfo);
|
||||
}
|
||||
|
||||
private String getClassFullName(ClassNode cls) {
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getFullName();
|
||||
}
|
||||
return getPackageName(clsInfo.getPackage()) + CLASS_NAME_SEPARATOR + getClassName(clsInfo);
|
||||
}
|
||||
|
||||
public Map<ClassInfo, DeobfClsInfo> getClsMap() {
|
||||
return clsMap;
|
||||
}
|
||||
|
||||
public Map<FieldInfo, String> getFldMap() {
|
||||
return fldMap;
|
||||
}
|
||||
|
||||
public Map<MethodInfo, String> getMthMap() {
|
||||
return mthMap;
|
||||
}
|
||||
|
||||
public PackageNode getRootPackage() {
|
||||
return rootPackage;
|
||||
}
|
||||
|
||||
private static boolean isR(ClassNode cls) {
|
||||
if (!cls.getClassInfo().getShortName().equals("R")) {
|
||||
return false;
|
||||
}
|
||||
if (!cls.getMethods().isEmpty() || !cls.getFields().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
for (MethodNode m : inner.getMethods()) {
|
||||
if (!m.getMethodInfo().isConstructor() && !m.getMethodInfo().isClassInit()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
ArgType type = field.getType();
|
||||
if (type != ArgType.INT && (!type.isArray() || type.getArrayElement() != ArgType.INT)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static jadx.core.utils.StringUtils.notEmpty;
|
||||
|
||||
public class NameMapper {
|
||||
|
||||
private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
|
||||
"\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
|
||||
|
||||
private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile(
|
||||
"(" + VALID_JAVA_IDENTIFIER + "\\.)*" + VALID_JAVA_IDENTIFIER);
|
||||
|
||||
private static final Set<String> RESERVED_NAMES = new HashSet<>(
|
||||
Arrays.asList(
|
||||
"abstract",
|
||||
"assert",
|
||||
"boolean",
|
||||
"break",
|
||||
"byte",
|
||||
"case",
|
||||
"catch",
|
||||
"char",
|
||||
"class",
|
||||
"const",
|
||||
"continue",
|
||||
"default",
|
||||
"do",
|
||||
"double",
|
||||
"else",
|
||||
"enum",
|
||||
"extends",
|
||||
"false",
|
||||
"final",
|
||||
"finally",
|
||||
"float",
|
||||
"for",
|
||||
"goto",
|
||||
"if",
|
||||
"implements",
|
||||
"import",
|
||||
"instanceof",
|
||||
"int",
|
||||
"interface",
|
||||
"long",
|
||||
"native",
|
||||
"new",
|
||||
"null",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"return",
|
||||
"short",
|
||||
"static",
|
||||
"strictfp",
|
||||
"super",
|
||||
"switch",
|
||||
"synchronized",
|
||||
"this",
|
||||
"throw",
|
||||
"throws",
|
||||
"transient",
|
||||
"true",
|
||||
"try",
|
||||
"void",
|
||||
"volatile",
|
||||
"while"));
|
||||
|
||||
public static boolean isReserved(String str) {
|
||||
return RESERVED_NAMES.contains(str);
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifier(String str) {
|
||||
return notEmpty(str)
|
||||
&& !isReserved(str)
|
||||
&& VALID_JAVA_IDENTIFIER.matcher(str).matches();
|
||||
}
|
||||
|
||||
public static boolean isValidFullIdentifier(String 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) {
|
||||
return 32 <= c && c <= 126;
|
||||
}
|
||||
|
||||
public static boolean isAllCharsPrintable(String str) {
|
||||
int len = str.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!isPrintableChar(str.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
public class PackageNode {
|
||||
|
||||
private static final char SEPARATOR_CHAR = '.';
|
||||
|
||||
private PackageNode parentPackage;
|
||||
private List<PackageNode> innerPackages = Collections.emptyList();
|
||||
|
||||
private final String packageName;
|
||||
private String packageAlias;
|
||||
|
||||
private String cachedPackageFullName;
|
||||
private String cachedPackageFullAlias;
|
||||
|
||||
public PackageNode(String packageName) {
|
||||
this.packageName = packageName;
|
||||
this.parentPackage = this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
if (cachedPackageFullName == null) {
|
||||
Deque<PackageNode> pp = getParentPackages();
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getName());
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getName());
|
||||
}
|
||||
cachedPackageFullName = result.toString();
|
||||
}
|
||||
return cachedPackageFullName;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
if (packageAlias != null) {
|
||||
return packageAlias;
|
||||
}
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
packageAlias = alias;
|
||||
}
|
||||
|
||||
public boolean hasAlias() {
|
||||
return packageAlias != null;
|
||||
}
|
||||
|
||||
public boolean hasAnyAlias() {
|
||||
if (hasAlias()) {
|
||||
return true;
|
||||
}
|
||||
if (parentPackage != this) {
|
||||
return parentPackage.hasAnyAlias();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getFullAlias() {
|
||||
if (cachedPackageFullAlias == null) {
|
||||
Deque<PackageNode> pp = getParentPackages();
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
if (!pp.isEmpty()) {
|
||||
result.append(pp.pop().getAlias());
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getAlias());
|
||||
}
|
||||
} else {
|
||||
result.append(this.getAlias());
|
||||
}
|
||||
cachedPackageFullAlias = result.toString();
|
||||
}
|
||||
return cachedPackageFullAlias;
|
||||
}
|
||||
|
||||
public PackageNode getParentPackage() {
|
||||
return parentPackage;
|
||||
}
|
||||
|
||||
public List<PackageNode> getInnerPackages() {
|
||||
return innerPackages;
|
||||
}
|
||||
|
||||
public void addInnerPackage(PackageNode pkg) {
|
||||
if (innerPackages.isEmpty()) {
|
||||
innerPackages = new ArrayList<>();
|
||||
}
|
||||
innerPackages.add(pkg);
|
||||
pkg.parentPackage = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets inner package node by name
|
||||
*
|
||||
* @param name inner package name
|
||||
* @return package node or {@code null}
|
||||
*/
|
||||
public PackageNode getInnerPackageByName(String name) {
|
||||
PackageNode result = null;
|
||||
for (PackageNode p : innerPackages) {
|
||||
if (p.getName().equals(name)) {
|
||||
result = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills stack with parent packages exclude root node
|
||||
*
|
||||
* @return stack with parent packages
|
||||
*/
|
||||
private Deque<PackageNode> getParentPackages() {
|
||||
Deque<PackageNode> pp = new ArrayDeque<>();
|
||||
|
||||
PackageNode currentPkg = this;
|
||||
PackageNode parentPkg = currentPkg.getParentPackage();
|
||||
while (currentPkg != parentPkg) {
|
||||
pp.push(currentPkg);
|
||||
currentPkg = parentPkg;
|
||||
parentPkg = currentPkg.getParentPackage();
|
||||
}
|
||||
return pp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return packageAlias;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
public enum AFlag {
|
||||
TRY_ENTER,
|
||||
TRY_LEAVE,
|
||||
|
||||
LOOP_START,
|
||||
LOOP_END,
|
||||
|
||||
SYNTHETIC,
|
||||
|
||||
RETURN, // block contains only return instruction
|
||||
ORIG_RETURN,
|
||||
|
||||
DONT_WRAP,
|
||||
DONT_INLINE,
|
||||
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||
RESTART_CODEGEN,
|
||||
DONT_RENAME, // do not rename during deobfuscation
|
||||
REMOVE, // can be completely removed
|
||||
ADDED_TO_REGION,
|
||||
|
||||
FINALLY_INSNS,
|
||||
|
||||
SKIP_FIRST_ARG,
|
||||
SKIP_ARG, // skip argument in invoke call
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
ANONYMOUS_CLASS,
|
||||
|
||||
THIS,
|
||||
|
||||
/**
|
||||
* RegisterArg attribute for method arguments
|
||||
*/
|
||||
METHOD_ARGUMENT,
|
||||
|
||||
/**
|
||||
* Type of RegisterArg or SSAVar can't be changed
|
||||
*/
|
||||
IMMUTABLE_TYPE,
|
||||
|
||||
CUSTOM_DECLARE, // variable for this register don't need declaration
|
||||
DECLARE_VAR,
|
||||
|
||||
ELSE_IF_CHAIN,
|
||||
|
||||
WRAPPED,
|
||||
ARITH_ONEARG,
|
||||
|
||||
FALL_THROUGH,
|
||||
|
||||
EXPLICIT_GENERICS,
|
||||
|
||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
|
||||
/**
|
||||
* Attribute types enumeration,
|
||||
* uses generic type for omit cast after 'AttributeStorage.get' method
|
||||
*
|
||||
* @param <T> attribute class implementation
|
||||
*/
|
||||
public class AType<T extends IAttribute> {
|
||||
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
||||
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
||||
|
||||
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
|
||||
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
|
||||
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
|
||||
|
||||
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
|
||||
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
|
||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
|
||||
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
|
||||
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
|
||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
|
||||
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
|
||||
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
|
||||
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
|
||||
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
|
||||
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
|
||||
|
||||
// method
|
||||
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||
|
||||
// registers
|
||||
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class AttrList<T> implements IAttribute {
|
||||
|
||||
private final AType<AttrList<T>> type;
|
||||
private final List<T> list = new ArrayList<>();
|
||||
|
||||
public AttrList(AType<AttrList<T>> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<T> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<AttrList<T>> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(list, "\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
|
||||
public abstract class AttrNode implements IAttributeNode {
|
||||
|
||||
private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage();
|
||||
|
||||
private AttributeStorage storage = EMPTY_ATTR_STORAGE;
|
||||
|
||||
@Override
|
||||
public void add(AFlag flag) {
|
||||
initStorage().add(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAttr(IAttribute attr) {
|
||||
initStorage().add(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
|
||||
initStorage().add(type, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyAttributesFrom(AttrNode attrNode) {
|
||||
AttributeStorage copyFrom = attrNode.storage;
|
||||
if (!copyFrom.isEmpty()) {
|
||||
initStorage().addAll(copyFrom);
|
||||
}
|
||||
}
|
||||
|
||||
private AttributeStorage initStorage() {
|
||||
AttributeStorage store = storage;
|
||||
if (store == EMPTY_ATTR_STORAGE) {
|
||||
store = new AttributeStorage();
|
||||
storage = store;
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
private void unloadIfEmpty() {
|
||||
if (storage.isEmpty() && storage != EMPTY_ATTR_STORAGE) {
|
||||
storage = EMPTY_ATTR_STORAGE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(AFlag flag) {
|
||||
return storage.contains(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> boolean contains(AType<T> type) {
|
||||
return storage.contains(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> T get(AType<T> type) {
|
||||
return storage.get(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation getAnnotation(String cls) {
|
||||
return storage.getAnnotation(cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> getAll(AType<AttrList<T>> type) {
|
||||
return storage.getAll(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(AFlag flag) {
|
||||
storage.remove(flag);
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> void remove(AType<T> type) {
|
||||
storage.remove(type);
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttr(IAttribute attr) {
|
||||
storage.remove(attr);
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes() {
|
||||
storage.clear();
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttributesStringsList() {
|
||||
return storage.getAttributeStrings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributesString() {
|
||||
return storage.toString();
|
||||
}
|
||||
|
||||
public boolean isAttrStorageEmpty() {
|
||||
return storage.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
* Storage for different attribute types:
|
||||
* 1. flags - boolean attribute (set or not)
|
||||
* 2. attribute - class instance associated with attribute type.
|
||||
*/
|
||||
public class AttributeStorage {
|
||||
|
||||
private final Set<AFlag> flags;
|
||||
private final Map<AType<?>, IAttribute> attributes;
|
||||
|
||||
public AttributeStorage() {
|
||||
flags = EnumSet.noneOf(AFlag.class);
|
||||
attributes = new IdentityHashMap<>();
|
||||
}
|
||||
|
||||
public void add(AFlag flag) {
|
||||
flags.add(flag);
|
||||
}
|
||||
|
||||
public void add(IAttribute attr) {
|
||||
attributes.put(attr.getType(), attr);
|
||||
}
|
||||
|
||||
public <T> void add(AType<AttrList<T>> type, T obj) {
|
||||
AttrList<T> list = get(type);
|
||||
if (list == null) {
|
||||
list = new AttrList<>(type);
|
||||
add(list);
|
||||
}
|
||||
list.getList().add(obj);
|
||||
}
|
||||
|
||||
public void addAll(AttributeStorage otherList) {
|
||||
flags.addAll(otherList.flags);
|
||||
attributes.putAll(otherList.attributes);
|
||||
}
|
||||
|
||||
public boolean contains(AFlag flag) {
|
||||
return flags.contains(flag);
|
||||
}
|
||||
|
||||
public <T extends IAttribute> boolean contains(AType<T> type) {
|
||||
return attributes.containsKey(type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends IAttribute> T get(AType<T> type) {
|
||||
return (T) attributes.get(type);
|
||||
}
|
||||
|
||||
public Annotation getAnnotation(String cls) {
|
||||
AnnotationsList aList = get(AType.ANNOTATION_LIST);
|
||||
return aList == null ? null : aList.get(cls);
|
||||
}
|
||||
|
||||
public <T> List<T> getAll(AType<AttrList<T>> type) {
|
||||
AttrList<T> attrList = get(type);
|
||||
if (attrList == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(attrList.getList());
|
||||
}
|
||||
|
||||
public void remove(AFlag flag) {
|
||||
flags.remove(flag);
|
||||
}
|
||||
|
||||
public <T extends IAttribute> void remove(AType<T> type) {
|
||||
attributes.remove(type);
|
||||
}
|
||||
|
||||
public void remove(IAttribute attr) {
|
||||
AType<? extends IAttribute> type = attr.getType();
|
||||
IAttribute a = attributes.get(type);
|
||||
if (a == attr) {
|
||||
attributes.remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
flags.clear();
|
||||
attributes.clear();
|
||||
}
|
||||
|
||||
public List<String> getAttributeStrings() {
|
||||
int size = flags.size() + attributes.size() + attributes.size();
|
||||
if (size == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> list = new ArrayList<>(size);
|
||||
for (AFlag a : flags) {
|
||||
list.add(a.toString());
|
||||
}
|
||||
for (IAttribute a : attributes.values()) {
|
||||
list.add(a.toString());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return flags.isEmpty() && attributes.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
List<String> list = getAttributeStrings();
|
||||
if (list.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
list.sort(String::compareTo);
|
||||
return "A[" + Utils.listToString(list) + ']';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
|
||||
public final class EmptyAttrStorage extends AttributeStorage {
|
||||
|
||||
@Override
|
||||
public boolean contains(AFlag flag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> boolean contains(AType<T> type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> T get(AType<T> type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation getAnnotation(String cls) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> getAll(AType<AttrList<T>> type) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(AFlag flag) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> void remove(AType<T> type) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(IAttribute attr) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttributeStrings() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
public interface IAttribute {
|
||||
AType<? extends IAttribute> getType();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
|
||||
public interface IAttributeNode {
|
||||
|
||||
void add(AFlag flag);
|
||||
|
||||
void addAttr(IAttribute attr);
|
||||
|
||||
<T> void addAttr(AType<AttrList<T>> type, T obj);
|
||||
|
||||
void copyAttributesFrom(AttrNode attrNode);
|
||||
|
||||
boolean contains(AFlag flag);
|
||||
|
||||
<T extends IAttribute> boolean contains(AType<T> type);
|
||||
|
||||
<T extends IAttribute> T get(AType<T> type);
|
||||
|
||||
Annotation getAnnotation(String cls);
|
||||
|
||||
<T> List<T> getAll(AType<AttrList<T>> type);
|
||||
|
||||
void remove(AFlag flag);
|
||||
|
||||
<T extends IAttribute> void remove(AType<T> type);
|
||||
|
||||
void removeAttr(IAttribute attr);
|
||||
|
||||
void clearAttributes();
|
||||
|
||||
List<String> getAttributesStringsList();
|
||||
|
||||
String getAttributesString();
|
||||
}
|
||||
+5
-6
@@ -1,12 +1,12 @@
|
||||
package jadx.dex.attributes.annotations;
|
||||
|
||||
import jadx.dex.instructions.args.ArgType;
|
||||
package jadx.core.dex.attributes.annotations;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class Annotation {
|
||||
|
||||
public static enum Visibility {
|
||||
public enum Visibility {
|
||||
BUILD, RUNTIME, SYSTEM
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ public class Annotation {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
|
||||
return "Annotation[" + visibility + ", " + atype + ", " + values + ']';
|
||||
}
|
||||
|
||||
}
|
||||
+15
-9
@@ -1,20 +1,23 @@
|
||||
package jadx.dex.attributes.annotations;
|
||||
|
||||
import jadx.dex.attributes.AttributeType;
|
||||
import jadx.dex.attributes.IAttribute;
|
||||
import jadx.utils.Utils;
|
||||
package jadx.core.dex.attributes.annotations;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class AnnotationsList implements IAttribute {
|
||||
|
||||
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList());
|
||||
|
||||
private final Map<String, Annotation> map;
|
||||
|
||||
public AnnotationsList(List<Annotation> anList) {
|
||||
map = new HashMap<String, Annotation>(anList.size());
|
||||
map = new HashMap<>(anList.size());
|
||||
for (Annotation a : anList) {
|
||||
map.put(a.getAnnotationClass(), a);
|
||||
}
|
||||
@@ -32,14 +35,17 @@ public class AnnotationsList implements IAttribute {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeType getType() {
|
||||
return AttributeType.ANNOTATION_LIST;
|
||||
public AType<AnnotationsList> getType() {
|
||||
return AType.ANNOTATION_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(map.values());
|
||||
}
|
||||
|
||||
}
|
||||
+8
-9
@@ -1,18 +1,18 @@
|
||||
package jadx.dex.attributes.annotations;
|
||||
|
||||
import jadx.dex.attributes.AttributeType;
|
||||
import jadx.dex.attributes.IAttribute;
|
||||
import jadx.utils.Utils;
|
||||
package jadx.core.dex.attributes.annotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class MethodParameters implements IAttribute {
|
||||
|
||||
private final List<AnnotationsList> paramList;
|
||||
|
||||
public MethodParameters(int paramCount) {
|
||||
paramList = new ArrayList<AnnotationsList>(paramCount);
|
||||
paramList = new ArrayList<>(paramCount);
|
||||
}
|
||||
|
||||
public List<AnnotationsList> getParamList() {
|
||||
@@ -20,13 +20,12 @@ public class MethodParameters implements IAttribute {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeType getType() {
|
||||
return AttributeType.ANNOTATION_MTH_PARAMETERS;
|
||||
public AType<MethodParameters> getType() {
|
||||
return AType.ANNOTATION_MTH_PARAMETERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(paramList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
* List of variables to be declared at region start.
|
||||
*/
|
||||
public class DeclareVariablesAttr implements IAttribute {
|
||||
|
||||
private final List<CodeVar> vars = new ArrayList<>();
|
||||
|
||||
public Iterable<CodeVar> getVars() {
|
||||
return vars;
|
||||
}
|
||||
|
||||
public void addVar(CodeVar arg) {
|
||||
vars.add(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<DeclareVariablesAttr> getType() {
|
||||
return AType.DECLARE_VARIABLES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DECL_VAR: " + Utils.listToString(vars);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrList;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public class EdgeInsnAttr implements IAttribute {
|
||||
|
||||
private final BlockNode start;
|
||||
private final BlockNode end;
|
||||
private final InsnNode insn;
|
||||
|
||||
public static void addEdgeInsn(BlockNode start, BlockNode end, InsnNode insn) {
|
||||
EdgeInsnAttr edgeInsnAttr = new EdgeInsnAttr(start, end, insn);
|
||||
start.addAttr(AType.EDGE_INSN, edgeInsnAttr);
|
||||
end.addAttr(AType.EDGE_INSN, edgeInsnAttr);
|
||||
}
|
||||
|
||||
public EdgeInsnAttr(BlockNode start, BlockNode end, InsnNode insn) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.insn = insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<AttrList<EdgeInsnAttr>> getType() {
|
||||
return AType.EDGE_INSN;
|
||||
}
|
||||
|
||||
public BlockNode getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public BlockNode getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EDGE_INSN: " + start + "->" + end + ' ' + insn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class EnumClassAttr implements IAttribute {
|
||||
|
||||
public static class EnumField {
|
||||
private final FieldInfo field;
|
||||
private final ConstructorInsn constrInsn;
|
||||
private final int startArg;
|
||||
private ClassNode cls;
|
||||
|
||||
public EnumField(FieldInfo field, ConstructorInsn co, int startArg) {
|
||||
this.field = field;
|
||||
this.constrInsn = co;
|
||||
this.startArg = startArg;
|
||||
}
|
||||
|
||||
public FieldInfo getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public ConstructorInsn getConstrInsn() {
|
||||
return constrInsn;
|
||||
}
|
||||
|
||||
public int getStartArg() {
|
||||
return startArg;
|
||||
}
|
||||
|
||||
public ClassNode getCls() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public void setCls(ClassNode cls) {
|
||||
this.cls = cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return field + "(" + constrInsn + ") " + cls;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<EnumField> fields;
|
||||
private MethodNode staticMethod;
|
||||
|
||||
public EnumClassAttr(int fieldsCount) {
|
||||
this.fields = new ArrayList<>(fieldsCount);
|
||||
}
|
||||
|
||||
public List<EnumField> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public MethodNode getStaticMethod() {
|
||||
return staticMethod;
|
||||
}
|
||||
|
||||
public void setStaticMethod(MethodNode staticMethod) {
|
||||
this.staticMethod = staticMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<EnumClassAttr> getType() {
|
||||
return AType.ENUM_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Enum fields: " + fields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
|
||||
public class EnumMapAttr implements IAttribute {
|
||||
|
||||
public static class KeyValueMap {
|
||||
private final Map<Object, Object> map = new HashMap<>();
|
||||
|
||||
public Object get(Object key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
void put(Object key, Object value) {
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Map<FieldNode, KeyValueMap> fieldsMap;
|
||||
|
||||
@Nullable
|
||||
public KeyValueMap getMap(FieldNode field) {
|
||||
if (fieldsMap == null) {
|
||||
return null;
|
||||
}
|
||||
return fieldsMap.get(field);
|
||||
}
|
||||
|
||||
public void add(FieldNode field, Object key, Object value) {
|
||||
KeyValueMap map = getMap(field);
|
||||
if (map == null) {
|
||||
map = new KeyValueMap();
|
||||
if (fieldsMap == null) {
|
||||
fieldsMap = new HashMap<>();
|
||||
}
|
||||
fieldsMap.put(field, map);
|
||||
}
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return fieldsMap == null || fieldsMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<EnumMapAttr> getType() {
|
||||
return AType.ENUM_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Enum fields map: " + fieldsMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
|
||||
public class FieldReplaceAttr implements IAttribute {
|
||||
|
||||
public enum ReplaceWith {
|
||||
CLASS_INSTANCE,
|
||||
VAR
|
||||
}
|
||||
|
||||
private final ReplaceWith replaceType;
|
||||
private final Object replaceObj;
|
||||
|
||||
public FieldReplaceAttr(ClassInfo cls) {
|
||||
this.replaceType = ReplaceWith.CLASS_INSTANCE;
|
||||
this.replaceObj = cls;
|
||||
}
|
||||
|
||||
public FieldReplaceAttr(InsnArg reg) {
|
||||
this.replaceType = ReplaceWith.VAR;
|
||||
this.replaceObj = reg;
|
||||
}
|
||||
|
||||
public ReplaceWith getReplaceType() {
|
||||
return replaceType;
|
||||
}
|
||||
|
||||
public ClassInfo getClsRef() {
|
||||
return (ClassInfo) replaceObj;
|
||||
}
|
||||
|
||||
public InsnArg getVarRef() {
|
||||
return (InsnArg) replaceObj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<FieldReplaceAttr> getType() {
|
||||
return AType.FIELD_REPLACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "REPLACE: " + replaceType + ' ' + replaceObj;
|
||||
}
|
||||
}
|
||||
+7
-6
@@ -1,7 +1,9 @@
|
||||
package jadx.dex.attributes;
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.dex.nodes.InsnNode;
|
||||
import jadx.utils.Utils;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class ForceReturnAttr implements IAttribute {
|
||||
|
||||
@@ -16,13 +18,12 @@ public class ForceReturnAttr implements IAttribute {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeType getType() {
|
||||
return AttributeType.FORCE_RETURN;
|
||||
public AType<ForceReturnAttr> getType() {
|
||||
return AType.FORCE_RETURN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FORCE_RETURN " + Utils.listToString(returnInsn.getArguments());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class IgnoreEdgeAttr implements IAttribute {
|
||||
|
||||
private final Set<BlockNode> blocks = new HashSet<>(3);
|
||||
|
||||
public Set<BlockNode> getBlocks() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public boolean contains(BlockNode block) {
|
||||
return blocks.contains(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<IgnoreEdgeAttr> getType() {
|
||||
return AType.IGNORE_EDGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IGNORE_EDGES: " + Utils.listToString(blocks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class JadxError implements Comparable<JadxError> {
|
||||
|
||||
private final String error;
|
||||
private final Throwable cause;
|
||||
|
||||
public JadxError(String error, Throwable cause) {
|
||||
this.error = Objects.requireNonNull(error);
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JadxError o) {
|
||||
return this.error.compareTo(o.getError());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JadxError other = (JadxError) o;
|
||||
return error.equals(other.error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return error.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append("JadxError: ");
|
||||
if (error != null) {
|
||||
str.append(error);
|
||||
str.append(' ');
|
||||
}
|
||||
if (cause != null) {
|
||||
str.append(cause.getClass());
|
||||
str.append(':');
|
||||
str.append(cause.getMessage());
|
||||
str.append('\n');
|
||||
str.append(Utils.getStackTrace(cause));
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
public class JumpInfo {
|
||||
|
||||
private final int src;
|
||||
private final int dest;
|
||||
|
||||
public JumpInfo(int src, int dest) {
|
||||
this.src = src;
|
||||
this.dest = dest;
|
||||
}
|
||||
|
||||
public int getSrc() {
|
||||
return src;
|
||||
}
|
||||
|
||||
public int getDest() {
|
||||
return dest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * dest + src;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JumpInfo other = (JumpInfo) obj;
|
||||
return dest == other.dest && src == other.src;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
|
||||
public abstract class LineAttrNode extends AttrNode {
|
||||
|
||||
private int sourceLine;
|
||||
|
||||
private int decompiledLine;
|
||||
|
||||
public int getSourceLine() {
|
||||
return sourceLine;
|
||||
}
|
||||
|
||||
public void setSourceLine(int sourceLine) {
|
||||
this.sourceLine = sourceLine;
|
||||
}
|
||||
|
||||
public int getDecompiledLine() {
|
||||
return decompiledLine;
|
||||
}
|
||||
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
}
|
||||
|
||||
public void copyLines(LineAttrNode lineAttrNode) {
|
||||
setSourceLine(lineAttrNode.getSourceLine());
|
||||
setDecompiledLine(lineAttrNode.getDecompiledLine());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user