├── LICENSE ├── README.md └── docs ├── .idea ├── compiler.xml ├── docs.iml ├── encodings.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── .nojekyll ├── CNAME ├── README.md ├── _coverpage.md ├── _media ├── fustack.png ├── icon.svg ├── onlinebook.svg ├── qrcode.png ├── tree.png ├── wechatcode.jpg └── wxbugstack.svg ├── _sidebar.md ├── assets ├── css │ ├── gitalk.css │ └── vue.css ├── img │ └── 2020 │ │ ├── 1.2.3.png │ │ ├── 2.2.4-1.png │ │ ├── 2.2.4-2.png │ │ ├── 2.2.4-3.png │ │ ├── 3.1-1.png │ │ ├── 3.1-2.png │ │ ├── 3.1-3.png │ │ ├── 3.1-4.png │ │ ├── 3.1-5.png │ │ ├── 6.2-1.png │ │ └── 8.1-1.png ├── js │ ├── docsify-pagination.min.js │ ├── docsify.min.js │ ├── gitalk.min.js │ ├── md5.js │ ├── prism-bash.js │ ├── prism-java.min.js │ └── zoom-image.js └── resources │ └── 图稿.pptx ├── favicon.ico ├── index.html └── notes ├── 10.0后向兼容.md ├── 10.1介绍.md ├── 10.2规则.md ├── 1引言.md ├── 2.0类.md ├── 2.1结构.md ├── 2.2接口和组件.md ├── 2.3工具.md ├── 3.0方法.md ├── 3.1结构.md ├── 3.2接口和组件.md ├── 3.3工具.md ├── 4.0元数据.md ├── 4.1泛型.md ├── 4.2注释.md ├── 4.3调试.md ├── 5.0后向兼容.md ├── 5.1引言.md ├── 5.2规则.md ├── 6.0类.md ├── 6.1接口和组件.md ├── 6.2组件合成.md ├── 7.0方法.md ├── 7.1接口和组件.md ├── 7.2组件合成.md ├── 8.0方法分析.md ├── 8.1介绍.md ├── 8.2组件与接口.md ├── 9.0元数据.md ├── 9.1泛型.md ├── 9.2注释.md ├── 9.3调试.md ├── A.0附录.md ├── A.1字节代码指.md ├── A.2子例程.md ├── A.3属性.md ├── A.4规则.md ├── A.5性能.md └── JVM-指令表.md /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 《ASM4 使用指南》| 沉淀、分享、成长,让自己和他人都能有所收获! 2 | 3 | > 本文档是作者小傅哥从网上资料获取整理,方便学习使用。ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。如果本文能为您提供帮助,请给予支持(关注、点赞、分享)! 4 | 5 | > 小傅哥,Java Developer,[:trophy:CSDN 博客专家](https://bugstack.blog.csdn.net),[:pencil2:GitChat专栏作者](https://gitbook.cn/gitchat/column/5e5d29ac3fbd2d3f5d05e05f) 6 | 7 |
8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 | * :memo: 目录 22 | * [第 1 章 - 引言](/docs/notes/1引言.md) 23 | * `第一部分 核心 API` 24 | * [第 2 章 - 类](notes/2.0类.md) 25 | * [2.1 结构](notes/2.1结构.md) 26 | * [2.2 接口和组件](notes/2.2接口和组件.md) 27 | * [2.3 工具](notes/2.3工具.md) 28 | * [第 3 章 - 方法](/docs/notes/3.0方法.md) 29 | * [3.1 结构](/docs/notes/3.1结构.md) 30 | * [3.2 接口和组件](/docs/notes/3.2接口和组件.md) 31 | * [3.3 工具](/docs/notes/3.3工具.md) 32 | * [第 4 章 - 元数据](/docs/notes/4.0元数据.md) 33 | * [4.1 泛型](/docs/notes/4.1泛型.md) 34 | * [4.2 注释](/docs/notes/4.2注释.md) 35 | * [4.3 调试](/docs/notes/4.3调试.md) 36 | * [第 5 章 - 后向兼容](/docs/notes/5.0后向兼容.md) 37 | * [5.1 引言](/docs/notes/5.1引言.md) 38 | * [5.2 规则](/docs/notes/5.2规则.md) 39 | * `第二部分 树 API` 40 | * [第 6 章 - 类](/docs/notes/6.0类.md) 41 | * [6.1 接口和组件](/docs/notes/6.1接口和组件.md) 42 | * [6.2 组件合成](/docs/notes/6.2组件合成.md) 43 | * [第 7 章 - 方法](/docs/notes/7.0方法.md) 44 | * [7.1 接口和组件](/docs/notes/7.1接口和组件.md) 45 | * [7.2 组件合成](/docs/notes/7.2组件合成.md) 46 | * [第 8 章 - 方法分析](/docs/notes/8.0方法分析.md) 47 | * [8.1 介绍](/docs/notes/8.1介绍.md) 48 | * [8.2 组件与接口](/docs/notes/8.2组件与接口.md) 49 | * [第 9 章 - 元数据](/docs/notes/9.0元数据.md) 50 | * [9.1 泛型](/docs/notes/9.1泛型.md) 51 | * [9.2 注释](/docs/notes/9.2注释.md) 52 | * [9.2 调试](/docs/notes/9.3调试.md) 53 | * [第 10 章 - 后向兼容](/docs/notes/10.0后向兼容.md) 54 | * [10.1 介绍](/docs/notes/10.1介绍.md) 55 | * [10.2 规则](/docs/notes/10.2规则.md) 56 | * [A. 附录](/docs/notes/A.0附录.md) 57 | * [A.1 字节代码指令](/docs/notes/A.1字节代码指.md) 58 | * [A.2 子例程](/docs/notes/A.2子例程.md) 59 | * [A.3 属性](/docs/notes/A.3属性.md) 60 | * [A.4 规则](/docs/notes/A.4规则.md) 61 | * [A.5 性能](/docs/notes/A.5性能.md) 62 | 63 | --- 64 | 65 | ## 转载分享 66 | 67 | 建立本开源项目的初衷是基于个人学习与工作中对 Java 相关技术栈的总结记录,在这里也希望能帮助一些在学习 Java 过程中遇到问题的小伙伴,如果您需要转载本仓库的一些文章到自己的博客,请按照以下格式注明出处,谢谢合作。 68 | 69 | ``` 70 | 作者:小傅哥 71 | 链接:https://bugstack.cn 72 | 来源:bugstack虫洞栈 73 | ``` 74 | 75 | ## 与我联系 76 | 77 | - **加群交流** 78 | 本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “小傅哥” 微信(fustack),备注:加群。 79 | 80 | 81 | 82 | - **公众号(bugstack虫洞栈)** 83 | 沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、DDD专题案例、源码分析等。 84 | 85 | 86 | 87 | ## 参与贡献 88 | 89 | 1. 如果您对本项目有任何建议或发现文中内容有误的,欢迎提交 issues 进行指正。 90 | 2. 对于文中我没有涉及到知识点,欢迎提交 PR。 91 | 92 | ## 致谢 93 | 94 | 感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与我联系。 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /docs/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/.idea/docs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /docs/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/.nojekyll -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | asm.itstack.org -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 《ASM4 使用指南》· 虫洞技术栈 2 | 3 | [![stars](https://badgen.net/github/stars/MyGitBooks/asm?icon=github&color=4ab8a1)](https://github.com/MyGitBooks/asm) [![forks](https://badgen.net/github/forks/MyGitBooks/asm?icon=github&color=4ab8a1)](https://github.com/MyGitBooks/asm) [](https://itstack.org/_media/qrcode.png?x-oss-process=style/may) 4 | 5 | > 本文档是作者小傅哥从网上资料获取整理,方便学习使用。ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。如果本文能为您提供帮助,请给予支持(关注、点赞、分享)! 6 | 7 | > 小傅哥,Java Developer,[:trophy:CSDN 博客专家](https://bugstack.blog.csdn.net),[:pencil2:GitChat专栏作者](https://gitbook.cn/gitchat/column/5e5d29ac3fbd2d3f5d05e05f) 8 | 9 | **如何支持:** 10 | - 关注公众号 [bugstack虫洞栈](https://itstack.org/_media/qrcode.png?x-oss-process=style/may) 11 | - 点击右上角Star :star: 给予关注 12 | - 分享给您身边更多的小伙伴 13 | 14 | ## 转载分享 15 | 16 | 建立本开源项目的初衷是基于个人学习与工作中对 Java 相关技术栈的总结记录,在这里也希望能帮助一些在学习 Java 过程中遇到问题的小伙伴,如果您需要转载本仓库的一些文章到自己的博客,请按照以下格式注明出处,谢谢合作。 17 | 18 | ``` 19 | 作者:小傅哥 20 | 链接:https://bugstack.cn 21 | 来源:bugstack虫洞栈 22 | ``` 23 | 24 | ## 与我联系 25 | 26 | - **加群交流** 27 | 本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “小傅哥” 微信,备注:加群。 28 | 29 | 30 | - **公众号** 31 | 沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、DDD专题案例、源码分析等。 32 | 33 | 34 | ## 参与贡献 35 | 36 | 1. 如果您对本项目有任何建议或发现文中内容有误的,欢迎提交 issues 进行指正。 37 | 2. 对于文中我没有涉及到知识点,欢迎提交 PR。 38 | 39 | ## 致谢 40 | 41 | 感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与我联系。 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](_media/tree.png) 2 | 3 | # ASM4 使用指南 4 | 5 | ## A Java bytecode engineering library 6 | 7 | - 本文档是作者小傅哥从网上资料获取整理,方便学习使用。ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。如果本文能为您提供帮助,请给予支持(关注、点赞、分享)! 8 | 9 | [![stars](https://badgen.net/github/stars/MyGitBooks/asm?icon=github&color=4ab8a1)](https://github.com/MyGitBooks/asm) [![forks](https://badgen.net/github/forks/MyGitBooks/asm?icon=github&color=4ab8a1)](https://github.com/MyGitBooks/asm) [](https://itstack.org/_media/qrcode.png?x-oss-process=style/may) 10 | 11 | [GitHub]() 12 | [开始阅读](README.md) 13 | 14 | -------------------------------------------------------------------------------- /docs/_media/fustack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/_media/fustack.png -------------------------------------------------------------------------------- /docs/_media/icon.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | -------------------------------------------------------------------------------- /docs/_media/onlinebook.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Book 17 | Book 18 | 在线阅读 19 | 在线阅读 20 | 21 | 23 | -------------------------------------------------------------------------------- /docs/_media/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/_media/qrcode.png -------------------------------------------------------------------------------- /docs/_media/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/_media/tree.png -------------------------------------------------------------------------------- /docs/_media/wechatcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/_media/wechatcode.jpg -------------------------------------------------------------------------------- /docs/_media/wxbugstack.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 公众号 17 | 公众号 18 | bugstack虫洞栈 19 | bugstack虫洞栈 20 | 21 | 23 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [:octocat: 首页](/README) 2 | * :memo: 目录 3 | * [第 1 章 - 引言](/notes/1引言.md) 4 | * `第一部分 核心 API` 5 | * [第 2 章 - 类](notes/2.0类.md) 6 | * [2.1 结构](notes/2.1结构.md) 7 | * [2.2 接口和组件](notes/2.2接口和组件.md) 8 | * [2.3 工具](notes/2.3工具.md) 9 | * [第 3 章 - 方法](/notes/3.0方法.md) 10 | * [3.1 结构](/notes/3.1结构.md) 11 | * [3.2 接口和组件](/notes/3.2接口和组件.md) 12 | * [3.3 工具](/notes/3.3工具.md) 13 | * [第 4 章 - 元数据](/notes/4.0元数据.md) 14 | * [4.1 泛型](/notes/4.1泛型.md) 15 | * [4.2 注释](/notes/4.2注释.md) 16 | * [4.3 调试](/notes/4.3调试.md) 17 | * [第 5 章 - 后向兼容](/notes/5.0后向兼容.md) 18 | * [5.1 引言](/notes/5.1引言.md) 19 | * [5.2 规则](/notes/5.2规则.md) 20 | * `第二部分 树 API` 21 | * [第 6 章 - 类](/notes/6.0类.md) 22 | * [6.1 接口和组件](/notes/6.1接口和组件.md) 23 | * [6.2 组件合成](/notes/6.2组件合成.md) 24 | * [第 7 章 - 方法](/notes/7.0方法.md) 25 | * [7.1 接口和组件](/notes/7.1接口和组件.md) 26 | * [7.2 组件合成](/notes/7.2组件合成.md) 27 | * [第 8 章 - 方法分析](/notes/8.0方法分析.md) 28 | * [8.1 介绍](/notes/8.1介绍.md) 29 | * [8.2 组件与接口](/notes/8.2组件与接口.md) 30 | * [第 9 章 - 元数据](/notes/9.0元数据.md) 31 | * [9.1 泛型](/notes/9.1泛型.md) 32 | * [9.2 注释](/notes/9.2注释.md) 33 | * [9.2 调试](/notes/9.3调试.md) 34 | * [第 10 章 - 后向兼容](/notes/10.0后向兼容.md) 35 | * [10.1 介绍](/notes/10.1介绍.md) 36 | * [10.2 规则](/notes/10.2规则.md) 37 | * [A. 附录](/notes/A.0附录.md) 38 | * [A.1 字节代码指令](/notes/A.1字节代码指.md) 39 | * [A.2 子例程](/notes/A.2子例程.md) 40 | * [A.3 属性](/notes/A.3属性.md) 41 | * [A.4 规则](/notes/A.4规则.md) 42 | * [A.5 性能](/notes/A.5性能.md) 43 | * [JVM指令表](/notes/JVM-指令表.md) 44 | -------------------------------------------------------------------------------- /docs/assets/css/vue.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}body:not(.ready){overflow:hidden}body:not(.ready) .app-nav,body:not(.ready)>nav,body:not(.ready) [data-cloak]{display:none}div#app{font-size:30px;font-weight:lighter;margin:40vh auto;text-align:center}div#app:empty:before{content:"Loading..."}.emoji{height:1.2rem;vertical-align:middle}.progress{background-color:var(--theme-color,#42b983);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:999999}.search .search-keyword,.search a:hover{color:var(--theme-color,#42b983)}.search .search-keyword{font-style:normal;font-weight:700}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#34495e;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:15px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}a[disabled]{cursor:not-allowed;opacity:.6}kbd{border:1px solid #ccc;border-radius:3px;display:inline-block;font-size:12px!important;line-height:12px;margin-bottom:3px;padding:3px 5px;vertical-align:middle}li input[type=checkbox]{margin:0 .2em .25em 0;vertical-align:middle}.app-nav{margin:25px 60px 0 0;position:absolute;right:0;text-align:right;z-index:10}.app-nav.no-badge{margin-right:25px}.app-nav p{margin:0}.app-nav>a{margin:0 1rem;padding:5px 0}.app-nav li,.app-nav ul{display:inline-block;list-style:none;margin:0}.app-nav a{color:inherit;font-size:16px;text-decoration:none;transition:color .3s}.app-nav a.active,.app-nav a:hover{color:var(--theme-color,#42b983)}.app-nav a.active{border-bottom:2px solid var(--theme-color,#42b983)}.app-nav li{display:inline-block;margin:0 1rem;padding:5px 0;position:relative}.app-nav li ul{background-color:#fff;border:1px solid;border-color:#ddd #ddd #ccc;border-radius:4px;box-sizing:border-box;display:none;max-height:calc(100vh - 61px);overflow-y:auto;padding:10px 0;position:absolute;right:-15px;text-align:left;top:100%;white-space:nowrap}.app-nav li ul li{display:block;font-size:14px;line-height:1rem;margin:8px 14px;white-space:nowrap}.app-nav li ul a{display:block;font-size:inherit;margin:0;padding:0}.app-nav li ul a.active{border-bottom:0}.app-nav li:hover ul{display:block}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner:hover .octo-arm{-webkit-animation:octocat-wave .56s ease-in-out;animation:octocat-wave .56s ease-in-out}.github-corner svg{color:#fff;fill:var(--theme-color,#42b983);height:80px;width:80px}main{display:block;position:relative;width:100vw;height:100%;z-index:0}main.hidden{display:none}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:#34495e}.anchor:hover{text-decoration:underline}.sidebar{border-right:1px solid rgba(0,0,0,.07);overflow-y:auto;padding:40px 0 0;position:absolute;top:0;bottom:0;left:0;transition:transform .25s ease-out;width:300px;z-index:20}.sidebar>h1{margin:0 auto 1rem;font-size:1.5rem;font-weight:300;text-align:center}.sidebar>h1 a{color:inherit;text-decoration:none}.sidebar>h1 .app-nav{display:block;position:static}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar li.collapse .app-sub-sidebar{display:none}.sidebar ul{margin:0 0 0 15px;padding:0}.sidebar li>p{font-weight:700;margin:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar ul li ul{padding-left:20px}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53.3%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53.3%,.1)}.sidebar-toggle{background-color:transparent;background-color:hsla(0,0%,100%,.8);border:0;outline:none;padding:10px;position:absolute;bottom:0;left:0;text-align:center;transition:opacity .3s;width:284px;z-index:30}.sidebar-toggle .sidebar-toggle-button:hover{opacity:.4}.sidebar-toggle span{background-color:var(--theme-color,#42b983);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;position:absolute;top:0;right:0;bottom:0;left:300px;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:800px;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section hr{border:none;border-bottom:1px solid #eee;margin:2em 0}.markdown-section iframe{border:1px solid #eee;width:1px;min-width:100%}.markdown-section table{border-collapse:collapse;border-spacing:0;display:block;margin-bottom:1rem;overflow:auto;width:100%}.markdown-section th{font-weight:700}.markdown-section td,.markdown-section th{border:1px solid #ddd;padding:6px 13px}.markdown-section tr{border-top:1px solid #ccc}.markdown-section p.tip,.markdown-section tr:nth-child(2n){background-color:#f8f8f8}.markdown-section p.tip{border-bottom-right-radius:2px;border-left:4px solid #f66;border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip:before{background-color:#f66;border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;height:20px;width:20px;text-align:center;top:14px}.markdown-section p.tip code{background-color:#efefef}.markdown-section p.tip em{color:#34495e}.markdown-section p.warn{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem}.markdown-section ul.task-list>li{list-style-type:none}body.close .sidebar{transform:translateX(-300px)}body.close .sidebar-toggle{width:auto}body.close .content{left:0}@media print{.app-nav,.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}.app-nav{margin-top:16px}.app-nav li ul{top:30px}main{height:auto;overflow-x:hidden}.sidebar{left:-300px;transition:transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:transform .25s ease}.app-nav,.github-corner{transition:transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto;padding:30px 30px 10px 10px}body.close .sidebar{transform:translateX(300px)}body.close .sidebar-toggle{background-color:hsla(0,0%,100%,.8);transition:background-color 1s;width:284px;padding:10px}body.close .content{transform:translateX(300px)}body.close .app-nav,body.close .github-corner{display:none}.github-corner:hover .octo-arm{-webkit-animation:none;animation:none}.github-corner .octo-arm{-webkit-animation:octocat-wave .56s ease-in-out;animation:octocat-wave .56s ease-in-out}}@-webkit-keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}section.cover{align-items:center;background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;display:none}section.cover.show{display:flex}section.cover.has-mask .mask{background-color:#fff;opacity:.8;position:absolute;top:0;height:100%;width:100%}section.cover .cover-main{flex:1;margin:-20px 16px 0;text-align:center;z-index:1}section.cover a{color:inherit}section.cover a,section.cover a:hover{text-decoration:none}section.cover p{line-height:1.5rem;margin:1em 0}section.cover h1{color:inherit;font-size:2.5rem;font-weight:300;margin:.625rem 0 2.5rem;position:relative;text-align:center}section.cover h1 a{display:block}section.cover h1 small{bottom:-.4375rem;font-size:1rem;position:absolute}section.cover blockquote{font-size:1.5rem;text-align:center}section.cover ul{line-height:1.8;list-style-type:none;margin:1em auto;max-width:500px;padding:0}section.cover .cover-main>p:last-child a{border-radius:2rem;border:1px solid var(--theme-color,#42b983);box-sizing:border-box;color:var(--theme-color,#42b983);display:inline-block;font-size:1.05rem;letter-spacing:.1rem;margin:.5rem 1rem;padding:.75em 2rem;text-decoration:none;transition:all .15s ease}section.cover .cover-main>p:last-child a:last-child{background-color:var(--theme-color,#42b983);color:#fff}section.cover .cover-main>p:last-child a:last-child:hover{color:inherit;opacity:.8}section.cover .cover-main>p:last-child a:hover{color:inherit}section.cover blockquote>p>a{border-bottom:2px solid var(--theme-color,#42b983);transition:color .3s}section.cover blockquote>p>a:hover{color:var(--theme-color,#42b983)}.sidebar,body{background-color:#fff}.sidebar{color:#364149}.sidebar li{margin:6px 0}.sidebar ul li a{color:#505d6b;font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li ul{padding:0}.sidebar ul li.active>a{border-right:2px solid;color:var(--theme-color,#42b983);font-weight:600}.app-sub-sidebar li:before{content:"-";padding-right:4px;float:left}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section strong{color:#2c3e50;font-weight:600}.markdown-section a{color:var(--theme-color,#42b983);font-weight:600}.markdown-section h1{font-size:2rem;margin:0 0 1rem}.markdown-section h2{font-size:1.75rem;margin:45px 0 .8rem}.markdown-section h3{font-size:1.5rem;margin:40px 0 .6rem}.markdown-section h4{font-size:1.25rem}.markdown-section h5{font-size:1rem}.markdown-section h6{color:#777;font-size:1rem}.markdown-section figure,.markdown-section p{margin:1.2em 0}.markdown-section ol,.markdown-section p,.markdown-section ul{line-height:1.6rem;word-spacing:.05rem}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section blockquote{border-left:4px solid var(--theme-color,#42b983);color:#858585;margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:600;margin-left:0}.markdown-section iframe{margin:1em 0}.markdown-section em{color:#7f8c8d}.markdown-section code{border-radius:2px;color:#e96900;font-size:.8rem;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section code,.markdown-section pre{background-color:#f8f8f8;font-family:Roboto Mono,Monaco,courier,monospace}.markdown-section pre{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;line-height:1.5rem;margin:1.2em 0;overflow:auto;padding:0 1.4rem;position:relative;word-wrap:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8e908c}.token.namespace{opacity:.7}.token.boolean,.token.number{color:#c76b29}.token.punctuation{color:#525252}.token.property{color:#c08b30}.token.tag{color:#2973b7}.token.string{color:var(--theme-color,#42b983)}.token.selector{color:#6679cc}.token.attr-name{color:#2973b7}.language-css .token.string,.style .token.string,.token.entity,.token.url{color:#22a2c9}.token.attr-value,.token.control,.token.directive,.token.unit{color:var(--theme-color,#42b983)}.token.function,.token.keyword{color:#e96900}.token.atrule,.token.regex,.token.statement{color:#22a2c9}.token.placeholder,.token.variable{color:#3d8fd1}.token.deleted{text-decoration:line-through}.token.inserted{border-bottom:1px dotted #202746;text-decoration:none}.token.italic{font-style:italic}.token.bold,.token.important{font-weight:700}.token.important{color:#c94922}.token.entity{cursor:help}.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;background-color:#f8f8f8;border-radius:2px;color:#525252;display:block;font-family:Roboto Mono,Monaco,courier,monospace;font-size:.8rem;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;padding:2.2em 5px;white-space:inherit}.markdown-section code:after,.markdown-section code:before{letter-spacing:.05rem}code .token{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;min-height:1.5rem;position:relative;left:auto}pre:after{color:#ccc;content:attr(data-lang);font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0} -------------------------------------------------------------------------------- /docs/assets/img/2020/1.2.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/1.2.3.png -------------------------------------------------------------------------------- /docs/assets/img/2020/2.2.4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/2.2.4-1.png -------------------------------------------------------------------------------- /docs/assets/img/2020/2.2.4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/2.2.4-2.png -------------------------------------------------------------------------------- /docs/assets/img/2020/2.2.4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/2.2.4-3.png -------------------------------------------------------------------------------- /docs/assets/img/2020/3.1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/3.1-1.png -------------------------------------------------------------------------------- /docs/assets/img/2020/3.1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/3.1-2.png -------------------------------------------------------------------------------- /docs/assets/img/2020/3.1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/3.1-3.png -------------------------------------------------------------------------------- /docs/assets/img/2020/3.1-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/3.1-4.png -------------------------------------------------------------------------------- /docs/assets/img/2020/3.1-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/3.1-5.png -------------------------------------------------------------------------------- /docs/assets/img/2020/6.2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/6.2-1.png -------------------------------------------------------------------------------- /docs/assets/img/2020/8.1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/assets/img/2020/8.1-1.png -------------------------------------------------------------------------------- /docs/assets/js/docsify-pagination.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e():"function"==typeof define&&define.amd?define(e):e()}(0,function(){"use strict";var t,c=(function(t,e){function n(t,e){return e.querySelector(t)}(e=t.exports=function(t,e){return n(t,e=e||document)}).all=function(t,e){return(e=e||document).querySelectorAll(t)},e.engine=function(t){if(!t.one)throw new Error(".one callback required");if(!t.all)throw new Error(".all callback required");return n=t.one,e.all=t.all,e}}(t={exports:{}},t.exports),t.exports);c.all,c.engine;try{var a=c}catch(t){a=c}var e=Element.prototype,r=e.matches||e.webkitMatchesSelector||e.mozMatchesSelector||e.msMatchesSelector||e.oMatchesSelector,s=function(t,e){if(!t||1!==t.nodeType)return!1;if(r)return r.call(t,e);for(var n=a.all(e,t.parentNode),i=0;i*{line-height:1;vertical-align:middle}.pagination-item-label svg{height:.8em;width:auto;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1px}.pagination-item--next{margin-left:auto;text-align:right}.pagination-item--next svg{margin-left:.5em}.pagination-item--previous svg{margin-right:.5em}.pagination-item-title{font-size:1.6em}.pagination-item-subtitle{text-transform:uppercase;opacity:.3}");var o=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},l=function(){function i(t,e){for(var n=0;n ul > li"),c("p",e)),this.hyperlink=m(t))}return l(n,[{key:"toJSON",value:function(){if(this.hyperlink)return{name:this.hyperlink.innerText,href:this.hyperlink.getAttribute("href"),chapterName:this.chapter&&this.chapter.innerText||""}}}]),n}();var x={container:function(){return'
'},inner:function(t,e){return[t.prev&&'\n \n ",t.next&&'\n \n "].filter(Boolean).join("")}};window.$docsify=window.$docsify||{},window.$docsify.plugins=[function(t,e){var n=u({},f,e.config.pagination||{});function i(){var t=c("."+d);t&&(t.innerHTML=x.inner(function(t,e){try{var n=t.route.path,i=h(c.all(".sidebar li a")).filter(function(t){return!s(t,".section-link")}),a=i.find(g(n)),r=h((p(a,"ul")||{}).children).filter(function(t){return"LI"===t.tagName.toUpperCase()}),o=e?i.findIndex(g(n)):r.findIndex(function(t){var e=m(t);return e&&g(n,e)}),l=e?i:r;return{prev:new v(l[o-1]).toJSON(),next:new v(l[o+1]).toJSON()}}catch(t){return{}}}(e,n.crossChapter),n))}t.afterEach(function(t){return t+x.container()}),t.doneEach(function(){return i()})}].concat(window.$docsify.plugins||[])}); -------------------------------------------------------------------------------- /docs/assets/js/md5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript MD5 3 | * https://github.com/blueimp/JavaScript-MD5 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * https://opensource.org/licenses/MIT 10 | * 11 | * Based on 12 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 13 | * Digest Algorithm, as defined in RFC 1321. 14 | * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 15 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 16 | * Distributed under the BSD License 17 | * See http://pajhome.org.uk/crypt/md5 for more info. 18 | */ 19 | 20 | /* global define */ 21 | 22 | /* eslint-disable strict */ 23 | 24 | ;(function($) { 25 | 'use strict' 26 | 27 | /** 28 | * Add integers, wrapping at 2^32. 29 | * This uses 16-bit operations internally to work around bugs in interpreters. 30 | * 31 | * @param {number} x First integer 32 | * @param {number} y Second integer 33 | * @returns {number} Sum 34 | */ 35 | function safeAdd(x, y) { 36 | var lsw = (x & 0xffff) + (y & 0xffff) 37 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16) 38 | return (msw << 16) | (lsw & 0xffff) 39 | } 40 | 41 | /** 42 | * Bitwise rotate a 32-bit number to the left. 43 | * 44 | * @param {number} num 32-bit number 45 | * @param {number} cnt Rotation count 46 | * @returns {number} Rotated number 47 | */ 48 | function bitRotateLeft(num, cnt) { 49 | return (num << cnt) | (num >>> (32 - cnt)) 50 | } 51 | 52 | /** 53 | * Basic operation the algorithm uses. 54 | * 55 | * @param {number} q q 56 | * @param {number} a a 57 | * @param {number} b b 58 | * @param {number} x x 59 | * @param {number} s s 60 | * @param {number} t t 61 | * @returns {number} Result 62 | */ 63 | function md5cmn(q, a, b, x, s, t) { 64 | return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b) 65 | } 66 | /** 67 | * Basic operation the algorithm uses. 68 | * 69 | * @param {number} a a 70 | * @param {number} b b 71 | * @param {number} c c 72 | * @param {number} d d 73 | * @param {number} x x 74 | * @param {number} s s 75 | * @param {number} t t 76 | * @returns {number} Result 77 | */ 78 | function md5ff(a, b, c, d, x, s, t) { 79 | return md5cmn((b & c) | (~b & d), a, b, x, s, t) 80 | } 81 | /** 82 | * Basic operation the algorithm uses. 83 | * 84 | * @param {number} a a 85 | * @param {number} b b 86 | * @param {number} c c 87 | * @param {number} d d 88 | * @param {number} x x 89 | * @param {number} s s 90 | * @param {number} t t 91 | * @returns {number} Result 92 | */ 93 | function md5gg(a, b, c, d, x, s, t) { 94 | return md5cmn((b & d) | (c & ~d), a, b, x, s, t) 95 | } 96 | /** 97 | * Basic operation the algorithm uses. 98 | * 99 | * @param {number} a a 100 | * @param {number} b b 101 | * @param {number} c c 102 | * @param {number} d d 103 | * @param {number} x x 104 | * @param {number} s s 105 | * @param {number} t t 106 | * @returns {number} Result 107 | */ 108 | function md5hh(a, b, c, d, x, s, t) { 109 | return md5cmn(b ^ c ^ d, a, b, x, s, t) 110 | } 111 | /** 112 | * Basic operation the algorithm uses. 113 | * 114 | * @param {number} a a 115 | * @param {number} b b 116 | * @param {number} c c 117 | * @param {number} d d 118 | * @param {number} x x 119 | * @param {number} s s 120 | * @param {number} t t 121 | * @returns {number} Result 122 | */ 123 | function md5ii(a, b, c, d, x, s, t) { 124 | return md5cmn(c ^ (b | ~d), a, b, x, s, t) 125 | } 126 | 127 | /** 128 | * Calculate the MD5 of an array of little-endian words, and a bit length. 129 | * 130 | * @param {Array} x Array of little-endian words 131 | * @param {number} len Bit length 132 | * @returns {Array} MD5 Array 133 | */ 134 | function binlMD5(x, len) { 135 | /* append padding */ 136 | x[len >> 5] |= 0x80 << len % 32 137 | x[(((len + 64) >>> 9) << 4) + 14] = len 138 | 139 | var i 140 | var olda 141 | var oldb 142 | var oldc 143 | var oldd 144 | var a = 1732584193 145 | var b = -271733879 146 | var c = -1732584194 147 | var d = 271733878 148 | 149 | for (i = 0; i < x.length; i += 16) { 150 | olda = a 151 | oldb = b 152 | oldc = c 153 | oldd = d 154 | 155 | a = md5ff(a, b, c, d, x[i], 7, -680876936) 156 | d = md5ff(d, a, b, c, x[i + 1], 12, -389564586) 157 | c = md5ff(c, d, a, b, x[i + 2], 17, 606105819) 158 | b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330) 159 | a = md5ff(a, b, c, d, x[i + 4], 7, -176418897) 160 | d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426) 161 | c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341) 162 | b = md5ff(b, c, d, a, x[i + 7], 22, -45705983) 163 | a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416) 164 | d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417) 165 | c = md5ff(c, d, a, b, x[i + 10], 17, -42063) 166 | b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162) 167 | a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682) 168 | d = md5ff(d, a, b, c, x[i + 13], 12, -40341101) 169 | c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290) 170 | b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329) 171 | 172 | a = md5gg(a, b, c, d, x[i + 1], 5, -165796510) 173 | d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632) 174 | c = md5gg(c, d, a, b, x[i + 11], 14, 643717713) 175 | b = md5gg(b, c, d, a, x[i], 20, -373897302) 176 | a = md5gg(a, b, c, d, x[i + 5], 5, -701558691) 177 | d = md5gg(d, a, b, c, x[i + 10], 9, 38016083) 178 | c = md5gg(c, d, a, b, x[i + 15], 14, -660478335) 179 | b = md5gg(b, c, d, a, x[i + 4], 20, -405537848) 180 | a = md5gg(a, b, c, d, x[i + 9], 5, 568446438) 181 | d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690) 182 | c = md5gg(c, d, a, b, x[i + 3], 14, -187363961) 183 | b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501) 184 | a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467) 185 | d = md5gg(d, a, b, c, x[i + 2], 9, -51403784) 186 | c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473) 187 | b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734) 188 | 189 | a = md5hh(a, b, c, d, x[i + 5], 4, -378558) 190 | d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463) 191 | c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562) 192 | b = md5hh(b, c, d, a, x[i + 14], 23, -35309556) 193 | a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060) 194 | d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353) 195 | c = md5hh(c, d, a, b, x[i + 7], 16, -155497632) 196 | b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640) 197 | a = md5hh(a, b, c, d, x[i + 13], 4, 681279174) 198 | d = md5hh(d, a, b, c, x[i], 11, -358537222) 199 | c = md5hh(c, d, a, b, x[i + 3], 16, -722521979) 200 | b = md5hh(b, c, d, a, x[i + 6], 23, 76029189) 201 | a = md5hh(a, b, c, d, x[i + 9], 4, -640364487) 202 | d = md5hh(d, a, b, c, x[i + 12], 11, -421815835) 203 | c = md5hh(c, d, a, b, x[i + 15], 16, 530742520) 204 | b = md5hh(b, c, d, a, x[i + 2], 23, -995338651) 205 | 206 | a = md5ii(a, b, c, d, x[i], 6, -198630844) 207 | d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415) 208 | c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905) 209 | b = md5ii(b, c, d, a, x[i + 5], 21, -57434055) 210 | a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571) 211 | d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606) 212 | c = md5ii(c, d, a, b, x[i + 10], 15, -1051523) 213 | b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799) 214 | a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359) 215 | d = md5ii(d, a, b, c, x[i + 15], 10, -30611744) 216 | c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380) 217 | b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649) 218 | a = md5ii(a, b, c, d, x[i + 4], 6, -145523070) 219 | d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379) 220 | c = md5ii(c, d, a, b, x[i + 2], 15, 718787259) 221 | b = md5ii(b, c, d, a, x[i + 9], 21, -343485551) 222 | 223 | a = safeAdd(a, olda) 224 | b = safeAdd(b, oldb) 225 | c = safeAdd(c, oldc) 226 | d = safeAdd(d, oldd) 227 | } 228 | return [a, b, c, d] 229 | } 230 | 231 | /** 232 | * Convert an array of little-endian words to a string 233 | * 234 | * @param {Array} input MD5 Array 235 | * @returns {string} MD5 string 236 | */ 237 | function binl2rstr(input) { 238 | var i 239 | var output = '' 240 | var length32 = input.length * 32 241 | for (i = 0; i < length32; i += 8) { 242 | output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff) 243 | } 244 | return output 245 | } 246 | 247 | /** 248 | * Convert a raw string to an array of little-endian words 249 | * Characters >255 have their high-byte silently ignored. 250 | * 251 | * @param {string} input Raw input string 252 | * @returns {Array} Array of little-endian words 253 | */ 254 | function rstr2binl(input) { 255 | var i 256 | var output = [] 257 | output[(input.length >> 2) - 1] = undefined 258 | for (i = 0; i < output.length; i += 1) { 259 | output[i] = 0 260 | } 261 | var length8 = input.length * 8 262 | for (i = 0; i < length8; i += 8) { 263 | output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32 264 | } 265 | return output 266 | } 267 | 268 | /** 269 | * Calculate the MD5 of a raw string 270 | * 271 | * @param {string} s Input string 272 | * @returns {string} Raw MD5 string 273 | */ 274 | function rstrMD5(s) { 275 | return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)) 276 | } 277 | 278 | /** 279 | * Calculates the HMAC-MD5 of a key and some data (raw strings) 280 | * 281 | * @param {string} key HMAC key 282 | * @param {string} data Raw input string 283 | * @returns {string} Raw MD5 string 284 | */ 285 | function rstrHMACMD5(key, data) { 286 | var i 287 | var bkey = rstr2binl(key) 288 | var ipad = [] 289 | var opad = [] 290 | var hash 291 | ipad[15] = opad[15] = undefined 292 | if (bkey.length > 16) { 293 | bkey = binlMD5(bkey, key.length * 8) 294 | } 295 | for (i = 0; i < 16; i += 1) { 296 | ipad[i] = bkey[i] ^ 0x36363636 297 | opad[i] = bkey[i] ^ 0x5c5c5c5c 298 | } 299 | hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8) 300 | return binl2rstr(binlMD5(opad.concat(hash), 512 + 128)) 301 | } 302 | 303 | /** 304 | * Convert a raw string to a hex string 305 | * 306 | * @param {string} input Raw input string 307 | * @returns {string} Hex encoded string 308 | */ 309 | function rstr2hex(input) { 310 | var hexTab = '0123456789abcdef' 311 | var output = '' 312 | var x 313 | var i 314 | for (i = 0; i < input.length; i += 1) { 315 | x = input.charCodeAt(i) 316 | output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f) 317 | } 318 | return output 319 | } 320 | 321 | /** 322 | * Encode a string as UTF-8 323 | * 324 | * @param {string} input Input string 325 | * @returns {string} UTF8 string 326 | */ 327 | function str2rstrUTF8(input) { 328 | return unescape(encodeURIComponent(input)) 329 | } 330 | 331 | /** 332 | * Encodes input string as raw MD5 string 333 | * 334 | * @param {string} s Input string 335 | * @returns {string} Raw MD5 string 336 | */ 337 | function rawMD5(s) { 338 | return rstrMD5(str2rstrUTF8(s)) 339 | } 340 | /** 341 | * Encodes input string as Hex encoded string 342 | * 343 | * @param {string} s Input string 344 | * @returns {string} Hex encoded string 345 | */ 346 | function hexMD5(s) { 347 | return rstr2hex(rawMD5(s)) 348 | } 349 | /** 350 | * Calculates the raw HMAC-MD5 for the given key and data 351 | * 352 | * @param {string} k HMAC key 353 | * @param {string} d Input string 354 | * @returns {string} Raw MD5 string 355 | */ 356 | function rawHMACMD5(k, d) { 357 | return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d)) 358 | } 359 | /** 360 | * Calculates the Hex encoded HMAC-MD5 for the given key and data 361 | * 362 | * @param {string} k HMAC key 363 | * @param {string} d Input string 364 | * @returns {string} Raw MD5 string 365 | */ 366 | function hexHMACMD5(k, d) { 367 | return rstr2hex(rawHMACMD5(k, d)) 368 | } 369 | 370 | /** 371 | * Calculates MD5 value for a given string. 372 | * If a key is provided, calculates the HMAC-MD5 value. 373 | * Returns a Hex encoded string unless the raw argument is given. 374 | * 375 | * @param {string} string Input string 376 | * @param {string} [key] HMAC key 377 | * @param {boolean} [raw] Raw output switch 378 | * @returns {string} MD5 output 379 | */ 380 | function md5(string, key, raw) { 381 | if (!key) { 382 | if (!raw) { 383 | return hexMD5(string) 384 | } 385 | return rawMD5(string) 386 | } 387 | if (!raw) { 388 | return hexHMACMD5(key, string) 389 | } 390 | return rawHMACMD5(key, string) 391 | } 392 | 393 | if (typeof define === 'function' && define.amd) { 394 | define(function() { 395 | return md5 396 | }) 397 | } else if (typeof module === 'object' && module.exports) { 398 | module.exports = md5 399 | } else { 400 | $.md5 = md5 401 | } 402 | })(this) 403 | -------------------------------------------------------------------------------- /docs/assets/js/prism-bash.js: -------------------------------------------------------------------------------- 1 | (function(Prism) { 2 | // $ set | grep '^[A-Z][^[:space:]]*=' | cut -d= -f1 | tr '\n' '|' 3 | // + LC_ALL, RANDOM, REPLY, SECONDS. 4 | // + make sure PS1..4 are here as they are not always set, 5 | // - some useless things. 6 | var envVars = '\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b'; 7 | var insideString = { 8 | 'environment': { 9 | pattern: RegExp("\\$" + envVars), 10 | alias: 'constant' 11 | }, 12 | 'variable': [ 13 | // [0]: Arithmetic Environment 14 | { 15 | pattern: /\$?\(\([\s\S]+?\)\)/, 16 | greedy: true, 17 | inside: { 18 | // If there is a $ sign at the beginning highlight $(( and )) as variable 19 | 'variable': [ 20 | { 21 | pattern: /(^\$\(\([\s\S]+)\)\)/, 22 | lookbehind: true 23 | }, 24 | /^\$\(\(/ 25 | ], 26 | 'number': /\b0x[\dA-Fa-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee]-?\d+)?/, 27 | // Operators according to https://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic 28 | 'operator': /--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/, 29 | // If there is no $ sign at the beginning highlight (( and )) as punctuation 30 | 'punctuation': /\(\(?|\)\)?|,|;/ 31 | } 32 | }, 33 | // [1]: Command Substitution 34 | { 35 | pattern: /\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/, 36 | greedy: true, 37 | inside: { 38 | 'variable': /^\$\(|^`|\)$|`$/ 39 | } 40 | }, 41 | // [2]: Brace expansion 42 | { 43 | pattern: /\$\{[^}]+\}/, 44 | greedy: true, 45 | inside: { 46 | 'operator': /:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/, 47 | 'punctuation': /[\[\]]/, 48 | 'environment': { 49 | pattern: RegExp("(\\{)" + envVars), 50 | lookbehind: true, 51 | alias: 'constant' 52 | } 53 | } 54 | }, 55 | /\$(?:\w+|[#?*!@$])/ 56 | ], 57 | // Escape sequences from echo and printf's manuals, and escaped quotes. 58 | 'entity': /\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/ 59 | }; 60 | 61 | Prism.languages.bash = { 62 | 'shebang': { 63 | pattern: /^#!\s*\/.*/, 64 | alias: 'important' 65 | }, 66 | 'comment': { 67 | pattern: /(^|[^"{\\$])#.*/, 68 | lookbehind: true 69 | }, 70 | 'function-name': [ 71 | // a) function foo { 72 | // b) foo() { 73 | // c) function foo() { 74 | // but not “foo {” 75 | { 76 | // a) and c) 77 | pattern: /(\bfunction\s+)\w+(?=(?:\s*\(?:\s*\))?\s*\{)/, 78 | lookbehind: true, 79 | alias: 'function' 80 | }, 81 | { 82 | // b) 83 | pattern: /\b\w+(?=\s*\(\s*\)\s*\{)/, 84 | alias: 'function' 85 | } 86 | ], 87 | // Highlight variable names as variables in for and select beginnings. 88 | 'for-or-select': { 89 | pattern: /(\b(?:for|select)\s+)\w+(?=\s+in\s)/, 90 | alias: 'variable', 91 | lookbehind: true 92 | }, 93 | // Highlight variable names as variables in the left-hand part 94 | // of assignments (“=” and “+=”). 95 | 'assign-left': { 96 | pattern: /(^|[\s;|&]|[<>]\()\w+(?=\+?=)/, 97 | inside: { 98 | 'environment': { 99 | pattern: RegExp("(^|[\\s;|&]|[<>]\\()" + envVars), 100 | lookbehind: true, 101 | alias: 'constant' 102 | } 103 | }, 104 | alias: 'variable', 105 | lookbehind: true 106 | }, 107 | 'string': [ 108 | // Support for Here-documents https://en.wikipedia.org/wiki/Here_document 109 | { 110 | pattern: /((?:^|[^<])<<-?\s*)(\w+?)\s*(?:\r?\n|\r)(?:[\s\S])*?(?:\r?\n|\r)\2/, 111 | lookbehind: true, 112 | greedy: true, 113 | inside: insideString 114 | }, 115 | // Here-document with quotes around the tag 116 | // → No expansion (so no “inside”). 117 | { 118 | pattern: /((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s*(?:\r?\n|\r)(?:[\s\S])*?(?:\r?\n|\r)\3/, 119 | lookbehind: true, 120 | greedy: true 121 | }, 122 | // “Normal” string 123 | { 124 | pattern: /(["'])(?:\\[\s\S]|\$\([^)]+\)|`[^`]+`|(?!\1)[^\\])*\1/, 125 | greedy: true, 126 | inside: insideString 127 | } 128 | ], 129 | 'environment': { 130 | pattern: RegExp("\\$?" + envVars), 131 | alias: 'constant' 132 | }, 133 | 'variable': insideString.variable, 134 | 'function': { 135 | pattern: /(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/, 136 | lookbehind: true 137 | }, 138 | 'keyword': { 139 | pattern: /(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/, 140 | lookbehind: true 141 | }, 142 | // https://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html 143 | 'builtin': { 144 | pattern: /(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/, 145 | lookbehind: true, 146 | // Alias added to make those easier to distinguish from strings. 147 | alias: 'class-name' 148 | }, 149 | 'boolean': { 150 | pattern: /(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/, 151 | lookbehind: true 152 | }, 153 | 'file-descriptor': { 154 | pattern: /\B&\d\b/, 155 | alias: 'important' 156 | }, 157 | 'operator': { 158 | // Lots of redirections here, but not just that. 159 | pattern: /\d?<>|>\||\+=|==?|!=?|=~|<<[<-]?|[&\d]?>>|\d?[<>]&?|&[>&]?|\|[&|]?|<=?|>=?/, 160 | inside: { 161 | 'file-descriptor': { 162 | pattern: /^\d/, 163 | alias: 'important' 164 | } 165 | } 166 | }, 167 | 'punctuation': /\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/, 168 | 'number': { 169 | pattern: /(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/, 170 | lookbehind: true 171 | } 172 | }; 173 | 174 | /* Patterns in command substitution. */ 175 | var toBeCopied = [ 176 | 'comment', 177 | 'function-name', 178 | 'for-or-select', 179 | 'assign-left', 180 | 'string', 181 | 'environment', 182 | 'function', 183 | 'keyword', 184 | 'builtin', 185 | 'boolean', 186 | 'file-descriptor', 187 | 'operator', 188 | 'punctuation', 189 | 'number' 190 | ]; 191 | var inside = insideString.variable[1].inside; 192 | for(var i = 0; i < toBeCopied.length; i++) { 193 | inside[toBeCopied[i]] = Prism.languages.bash[toBeCopied[i]]; 194 | } 195 | 196 | Prism.languages.shell = Prism.languages.bash; 197 | })(Prism); 198 | -------------------------------------------------------------------------------- /docs/assets/js/prism-java.min.js: -------------------------------------------------------------------------------- 1 | !function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|null|open|opens|package|private|protected|provides|public|requires|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,a=/\b[A-Z](?:\w*[a-z]\w*)?\b/;e.languages.java=e.languages.extend("clike",{"class-name":[a,/\b[A-Z]\w*(?=\s+\w+\s*[;,=())])/],keyword:t,function:[e.languages.clike.function,{pattern:/(\:\:)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x[\da-f_]*\.?[\da-f_p+-]+\b|(?:\b\d[\d_]*\.?[\d_]*|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0}}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"}}),e.languages.insertBefore("java","class-name",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0},namespace:{pattern:/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)[a-z]\w*(?:\.[a-z]\w*)+/,lookbehind:!0,inside:{punctuation:/\./}},generics:{pattern:/<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<[\w\s,.&?]*>)*>)*>)*>/,inside:{"class-name":a,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}}})}(Prism); -------------------------------------------------------------------------------- /docs/assets/js/zoom-image.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | /*! medium-zoom 1.0.4 | MIT License | https://github.com/francoischalifour/medium-zoom */var _extends=Object.assign||function(a){for(var b,c=1;cv.scrollOffset&&setTimeout(n,150);}},g=function(){var a=0 2 | 3 | 4 | 5 | 《ASM4 使用指南》 · 虫洞技术栈 | 沉淀、分享、成长,让自己和他人都能有所收获 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 |
23 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /docs/notes/10.0后向兼容.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/notes/10.0后向兼容.md -------------------------------------------------------------------------------- /docs/notes/10.1介绍.md: -------------------------------------------------------------------------------- 1 | # 10.1 介绍 2 | 3 | 与核心 API 的情景一样,在 ASM 4.0 的树 API 中已经引入了一种新机制,用于确保未来ASM 版本的后向兼容性。但要再次强调,仅靠 ASM 自身不能保证这一性质。它要求用户在编写代码时遵循一些简单的规则。本章的目标就是介绍这些规则,并大致介绍 ASM 树 API 中用于确保后向兼容的内部机制。 -------------------------------------------------------------------------------- /docs/notes/10.2规则.md: -------------------------------------------------------------------------------- 1 | # 10.2 规则 2 | 3 | 本节给出一些规则,在使用 ASM 树 API 时,要想确保你的代码在所有未来 ASM 版本中都保持有效(其意义见 5.1.1 节定义的约定),就必须遵循这些规则。 4 | 5 | 首先,如果使用树 API 编写一个类生成器,那就不需要遵循什么规则(和核心 API 一样)。可以用任意构造器创建 ClassNode 和其他元素,可以使用这些类的任意方法。 6 | 7 | 另一方面,如果要用树 API 编写类分析器或类适配器,也就是说,如果使用 ClassNode 或其他直接或间接地通过 ClassReader.accept()填充的类似类,或者如果重写这些类中的一个,则必须遵循下面给出的规则。 8 | 9 | ## 10.2.1 基本规则 10 | 11 | 1. 创建类节点 12 | 13 | 考虑这样一种情景,我们创建一个 ```ClassNode```,通过一个 ```ClassReader``` 填充它,然后分析或转换它,最终根据需要用 ```ClassWriter``` 写出结果(这一讨论及相关规则同样适用于其他节点类;对于由别人创建的 ClassNode,其分析或转换在下一节讨论)。在这种情况下,仅有一条规则: 14 | 15 | 规则 3:要用 ASM 版本X 的树 API 编写类分析器或适配器,则使用以这一确切版本为参数的构造器创建 ClassNode(而不是使用没有参数的默认构造器)。 16 | 17 | 本规则的目的是在通过一个 ClassReader 填充 ClassNode 时,如果遇到未知特性,则抛出一个错误(根据后向兼容性约定的定义)。如果不遵循这一规则,在以后遇到未知元素时,你 的分析或转换代码可能会失败,也许能够成功运行,但却因为没有忽略这些未知元素而给出错误结果。换言之,如果不遵循这一规则,可能无法保证约定的最后一项条款。 18 | 19 | 如何做到呢?ASM 4.0 内部对 ClassNode 的实现如下(这里重复使用 5.1.2 节的示例): 20 | 21 | ```java 22 | public class ClassNode extends ClassVisitor { 23 | public ClassNode() { 24 | super(ASM4, null); 25 | } 26 | 27 | public ClassNode(int api) { 28 | super(api, null); 29 | } 30 | ... 31 | 32 | public void visitSource(String source, String debug) { 33 | // 将 source 和 debug 存储在局部字段中... 34 | } 35 | } 36 | ``` 37 | 38 | 在 ASM 5.0 中,这一代码变为: 39 | 40 | ```java 41 | public class ClassNode extends ClassVisitor { 42 | ... 43 | 44 | public void visitSource(String source, String debug) { 45 | if (api < ASM5) { 46 | // 将source 和 debug 存储在局部字段中... 47 | } else { 48 | visitSource(null, source, debug); 49 | } 50 | } 51 | 52 | public void visitSource(Sring author, String source, String debug) { 53 | if (api < ASM5) { 54 | if (author == null) visitSource(source, debug); 55 | else 56 | throw new RuntimeException(); 57 | } else { 58 | // 将author、source 和 debug 存储在局部字段中... 59 | } 60 | } 61 | 62 | public void visitLicense(String license) { 63 | if (api < ASM5) throw new RuntimeException(); 64 | // 将 license 存储在局部字段中 65 | } 66 | } 67 | ``` 68 | 69 | 如果使用 ASM 4.0,那创建 ClassNode(ASM4)没有什么特别之处。但如果升级到 ASM 5.0,但不修改代码,那就会得到一个 ClassNode 5.0,它的 api 字段将为 ASM4 < ASM5。于是容易看出,如果输入类包含一个非 null 作者或许可属性,那通过 ClassReader 填充ClassNode 时将会失败,如约定中的定义。如果还升级你的代码,将 api 字段改为 ASM5,并升级剩余代码,将这些新属性考虑在内,那在填充代码时就不会抛出错误。 70 | 71 | 注意,ClassNode 5.0 代码非常类似于 ClassVisitor 5.0 代码。这是为了确保在定义 ClassNode 的子类时能够拥有正确的语义(类似于 ClassVisitor 的子类——见 10.2.2 节)。 72 | 73 | 2. 使用现有类代码 74 | 75 | 如果你的类分析器或适配器收到别人创建的 ClassNode,那你就不能肯定在创建它时传送 给其构造器的 ASM 版本。当然可以自行检查 api 字段,但如果发现这个版本高于你支持的版本, 直接拒绝这个类可能太过保守了。事实上,这个类中可能没有包含任何未知特性。另一方面,你不能检查是否存在未知特性(在我们的示例情景中,在为 ASM 4.0 编写代码时,你如何判断你的 ClassNode 中不存在未知的 license 字段呢?因为你在这里还不知道未来会添加这样一个字段)。于是设计了 ClassNode.check()方法来解决这个问题。这就引出了以下规则: 76 | 77 | 规则 4:要用 ASM 版本 X 的树 API 编写一个类分析器或适配器,使用别人创建的ClassNode,在以任何方式使用这个 ClassNode 之前,都要以这个确切版本号为参数,调用它的 check()方法。 78 | 79 | 其目的与规则 3 相同:如果不遵循这一规则,可能无法保证约定的最后一项条款。如何做到的呢?这个检查方法在 ASM 4.0 内部的实现如下: 80 | 81 | ```java 82 | public class ClassNode extends ClassVisitor { 83 | ... 84 | 85 | public void check(int api) { 86 | // 不做任何事 87 | } 88 | } 89 | ``` 90 | 91 | 在 ASM 5.0中,这一代码变为: 92 | 93 | ```java 94 | public class ClassNode extends ClassVisitor { 95 | ... 96 | 97 | public void check(int api) { 98 | if (api < ASM5 && (author != null || license != null)) { 99 | throw new RuntimeException(); 100 | } 101 | } 102 | } 103 | ``` 104 | 105 | 如果你的代码是为 ASM 4.0 编写的,而且如果得到一个 ClassNode 4.0,它的 api 字段将为 ASM4,这样不会有问题,check 也不做任何事情。但如果你得到一个 ClassNode 5.0,如果这个节点实际上包含了非 null author 或 license,也就是说,它包含了 ASM 4.0 中未知的新特性,那 check(ASM4)方法将会失败。 106 | 107 | *注意:如果你自己创建 ClassNode,也可以使用这一规则。那就不需要遵循规则 3,也就是说,不需要在ClassNode 构造器中指明 ASM 版本。这一检查将在 check 方法中进行(但在填充 ClassNode 时,这种做法的效率要低于在之前进行检查)。* 108 | 109 | ## 10.2.2 继承规则 110 | 111 | 如果希望提供 ClassNode 的子类或者其他类似节点类,那么规则 1 和 2 都是适用的。注意, 在一个 MethodNode 匿名子类的一个常用特例中,visitEnd()方法被重写: 112 | 113 | ```java 114 | class MyClassVisitor extends ClassVisitor { 115 | ... 116 | 117 | public MethodVisitor visitMethod(...) { 118 | final MethodVisitor mv = super.visitMethod(...); 119 | 120 | if (mv != null) { 121 | return new MethodNode(ASM4) { 122 | public void visitEnd() { 123 | // perform a transformation accept(mv); 124 | } 125 | }; 126 | } 127 | return mv; 128 | } 129 | } 130 | ``` 131 | 132 | 那就自动适用规则 2(匿名类不能被重写,尽管没有明确将它声明为 final 的)。你只需要遵循规则 3,也就是说,在 MehtodNode 构造器中指定 ASM 版本(或者遵循规则 4,也就是在执行转换之前调用 check(ASM4))。 133 | 134 | ## 10.2.3 其他包 135 | 136 | **asm.util** 和 **asm.commons** 中的类都有两个构造函数变体:一个有 ASM 版本参数,一个没有。 137 | 138 | 如果只是希望像 asm.util 中的 ASMifier、Textifier 或 CheckXxx Adapter 类或者asm.commons 包中的任意类一样,加以实例化和应用,那可以用没有 ASM 版本参数的构造器来实例化它们。也可以使用带有 ASM 版本参数的构造器,那就会不必要地将这些组件限制于特定的 ASM 版本(而使用无参数构造器相当于在说“使用最新的 ASM 版本”)。这就是为什么使用 ASM 版本参数的构造器被声明为 protected。 139 | 140 | 另一方面,如果希望重写 asm.util 中的 ASMifier、Textifier 或 CheckXxx Adapter 类或者 asm.commons 包中的任意类,那适用规则 1 和 2。具体来说,你的构造器必须以你希望用作参数的 ASM 版本来调用 super(…)。 141 | 142 | 最后,如果希望使用或重写 asm.tree.analysis 中的 Interpreter 类或其子类,必须做出同样的区分。还要注意,在使用这个分析包之前,创建一个 MethodNode 或者从别人那里获取一个,那在将这一代码传送给 Analyzer 之前必须使用规则 3 和 4。 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /docs/notes/1引言.md: -------------------------------------------------------------------------------- 1 | # 引言 2 | 3 | ## 1.1 动机 4 | 5 | 程序分析、程序生成和程序转换都是非常有用的技术,可在许多应用环境下使用: 6 | 7 | - 程序分析,既可能只是简单的语法分析(syntaxic parsing),也可能是完整的语义分析 (sematic analysis),可用于查找应用程序中的潜在 bug、检测未被用到的代码、对代码实施逆向工程,等等。 8 | - 程序生成,在编译器中使用。这些编译器不仅包括传统编译器,还包括用于分布式程序设计的 stub 编译器或 skeleton 编译器,以及 JIT(即时)编译器,等等。 9 | - 程序转换可,用于优化或混淆(obfuscate)程序、向应用程序中插入调试或性能监视代码,用于面向方面的程序设计,等等。 10 | 11 | 所有这些技术都可针对任意程序设计语言使用,但对于不同语言,其使用的难易程度可能会有所不同。对于 Java 语言,它们可用于 Java 源代码或编译后的 Java 类。在使用经过编译的类时, 其好处之一显然就是不需要源代码。因此,程序转换可用于任何应用程序,既包括保密的源代码, 也包含商业应用程序。使用已编译类的另一个好处是,有可能在运行时,在马上就要将类加载到 12 | Java 虚拟机之前,对类进行分析、生成或转换(在运行时生成和编译源代码也可以,但其速度很慢,而且需要一个完整的 Java 编译器)。其好处是,诸如 stub 编译器或方面编织器等工具对用户变为透明。 13 | 14 | 由于程序分析、生成和转换技术的用途众多,所以人们针对许多语言实现了许多用于分析、生成和转换程序的工具,这些语言中就包括 Java 在内。ASM 就是为 Java 语言设计的工具之一, 用于进行运行时(也是脱机的)类生成与转换。于是,人们设计了 ASM①库,用于处理经过编译的 Java 类。这个库的设计使其尽可能保持快速和小型化。对于那些在运行时使用 ASM 进行动态类生成或转换的应用程序来说,尽可能提高库的运行速度是非常重要的,这样可以保证这些应用程序的速度不致下降过多。而保持 ASM 库的小型化也非常重要,一方面是为了在内存有限的环境中使用,另一方面,也为了避免使那些使用 ASM 的小型应用程序或库增大过多。 15 | ASM 并不是惟一可生成和转换已编译 Java 类的工具,但它是最新、最高效的工具之一,可从 http://asm.objectweb.org 下载。其主要优点如下: 16 | 17 | ① ASM 的名字没有任何含义:它只是引用 C 语言中的 语言编写的函数。asm 关键字,这个关键字允许执行一些用汇编 18 | 19 | - 有一个简单的模块 API,设计完善、使用方便。 20 | - 文档齐全,拥有一个相关的 Eclipse 插件。 21 | - 支持最新的 Java 版本——Java 7。 22 | - 小而快、非常可靠。 23 | - 拥有庞大的用户社区,可以为新用户提供支持。 24 | - 源许可开放,几乎允许任意使用。 25 | 26 | ## 1.2 概述 27 | 28 | ### 1.2.1 范围 29 | 30 | ASM 库的目的是生成、转换和分析以字节数组表示的已编译 Java 类(它们在磁盘中的存储和在 Java 虚拟机中的加载都采用这种字节数组形式)。为此,ASM 提供了一些工具,使用高于字节级别的概念来读写和转换这种字节数组,这些概念包括数值常数、字符串、Java 标识符、Java 类型、Java 类结构元素,等等。注意,ASM 库的范围严格限制于类的读、写、转换和分析。具体来说,类的加载过程就超出了它的范围之外。 31 | 32 | ### 1.2.2 模型 33 | 34 | ASM 库提供了两个用于生成和转换已编译类的 API,一个是核心 API,以基于事件的形式来表示类,另一个是树 API,以基于对象的形式来表示类。 35 | 36 | 在采用基于事件的模型时,类是用一系列事件来表示的,每个事件表示类的一个元素,比如它的一个标头、一个字段、一个方法声明、一条指令,等等。基于事件的 API 定义了一组可能事件,以及这些事件必须遵循的发生顺序,还提供了一个类分析器,为每个被分析元素生成一个事件,还提供一个类写入器,由这些事件的序列生成经过编译的类。 37 | 38 | 而在采用基于对象的模型时,类用一个对象树表示,每个对象表示类的一部分,比如类本身、一个字段、一个方法、一条指令,等等,每个对象都有一些引用,指向表示其组成部分的对象。基于对象的 API 提供了一种方法,可以将表示一个类的事件序列转换为表示同一个类的对象树, 也可以反过来,将对象树表示为等价的事件序列。换言之,基于对象的 API 构建在基于事件的 API 之上。 39 | 40 | 这两个 API 可以与“用于 XML 的简单 API”(Simple API for XML,SAX)和用于 XML 文档的“文档对象模型(Document Object Model,DOM)API”相比较:基于事件的 API 类似于 SAX,而基于对象的 API 类似于 DOM。基于对象的 API 构建在基于事件的 API 之上,类似于 DOM 可在 SAX 的上层提供。 41 | 42 | ASM 之所以要提供两个 API,是因为没有哪种 API 是最佳的。实际上,每个 API 都有自己的优缺点: 43 | 44 | - 基于事件的 API 要快于基于对象的 API,所需要的内存也较少,因为它不需要在内存中创建和存储用于表示类的对象树(SAX 与 DOM 之间也有同样的差异)。 45 | - 但在使用基于事件的 API 时,类转换的实现可能要更难一些,因为在任意给定时刻, 类中只有一个元素可供使用(也就是与当前事件对应的元素),而在使用基于对象的 API 时,可以在内存中获得整个类。 注意,这两个 API 都是仅能同时维护一个类,而且独立于其他类,也就是说,它们不会维护有关类层级结构的信息,如果类的转换影响到其他类,那其他这些类的修改应当由用户负责完成。 46 | 47 | 注意,这两个 API 都是仅能同时维护一个类,而且独立于其他类,也就是说,它们不会维护有关类层级结构的信息,如果类的转换影响到其他类,那其他这些类的修改应当由用户负责完成。 48 | 49 | ### 1.2.3 体系结构 50 | 51 | ASM 应用程序拥有一个很强壮的体系结构方面(aspect)。事实上,对于基于事件的 API,其组织结构是围绕事件生成器(类分析器)、事件使用器(类写入器)和各种预定义的事件筛选器进行的,在这一结构中可以添加用户定义的生成器、使用器和筛选器。因此,这一 API 的使用分为两个步骤: 52 | 53 | - 将事件生成器、筛选器和使用器组件组装为可能很复杂的体系结构, 54 | - 然后启动事件生成器,以执行生成或转换过程。 55 | 56 | 基于对象的 API 也有一个体系结构方面:实际上,用于操作类树的类生成器或转换器组件是可以组成形成的,它们之间的链接代表着转换的顺序。 57 | 58 | 尽管典型 ASM 应用程序中的大多数组件体系结构都非常简单,但还是可以想象一下类似于如下所示的复杂体系结构,其中的箭头表示在类分析器、写入器或转换器之间进行的基于事件或基于对象的通信,在整个链中的任何位置,都可能会在基于事件与基于对象的表示之间进行转换: 59 | 60 | ![](http://asm.itstack.org/assets/img/2020/1.2.3.png) 61 | 62 | ## 1.3 组织形式 63 | 64 | ASM 库划分为几个包,以几个 jar 文件的形式进行分发: 65 | - org.objectweb.asm 和 org.objectweb.asm.signature 包定义了基于事件的 API,并提供了类分析器和写入器组件。它们包含在 asm.jar 存档文件中。 66 | - org.objectweb.asm.util 包,位于 asm-util.jar 存档文件中,提供各种基于核心 API 的工具,可以在开发和调试 ASM 应用程序时使用。 67 | 68 | - org.objectweb.asm.commons 包提供了几个很有用的预定义类转换器,它们大多是基于核心 API 的。这个包包含在 asm-commons.jar 存档文件中。 69 | - org.objectweb.asm.tree 包,位于 asm-tree.jar 存档文件中,定义了基于对象的 API,并提供了一些工具,用于在基于事件和基于对象的表示方法之间进行转换。 70 | - org.objectweb.asm.tree.analysis 包提供了一个类分析框架和几个预定义的类分析器,它们以树 API 为基础。这个包包含在 asm-analysis.jar 存档文件中。 71 | 72 | 本文档分为两部分。第一部分介绍核心 API,即 asm、asm-util 和 asm-commons 存档文件。第二部分介绍树 API,即 asm-tree 和 asm-analysis 存档文件。每部分至少包含该 API 与类相关的一章内容、该 API 与方法相关的一章内容、该 API 与注释、泛型等相关的一章内容。每章都会介绍编程接口及相关的工具与预定义组件。所有示例的源代码都可以从 ASM 网站上获得。 73 | 74 | 这种组织形式便于循序渐进地介绍类文件特征,但有时需要将同一个 ASM 类的介绍分散到几节中。因此,建议依次阅读本文档。如需有关 ASM API 的参考手册,请使用 Javadoc。 75 | 76 | **印刷约定** 77 | 78 | - 斜体用于强调句子中的元素。(译者注:中文中一般改为加粗显示) 79 | - 定宽字体用于表示代码段。 80 | - 粗体定宽字体用于强调代码元素。 81 | - 斜体定宽字体用于表示标记和代码中的变量部分。 82 | 83 | ## 1.4 致谢 84 | 85 | 感谢 François Horn 在制作本文档期间提供的宝贵评论,这些意见极大地提升了本文档的结构和可读性。 86 | -------------------------------------------------------------------------------- /docs/notes/2.0类.md: -------------------------------------------------------------------------------- 1 | 本章说明如何使用核心 ASM API 来生成和转换经过编译的 Java 类。首先介绍已编译类,然后将利用大量说明性示例,介绍用于生成和转换已编译类的相应 ASM 接口、组件和工具。方法、注释和泛型的内容将在之后各章中说明。 -------------------------------------------------------------------------------- /docs/notes/2.1结构.md: -------------------------------------------------------------------------------- 1 | # 2.1 结构 2 | 3 | ## 2.1.1 概述 4 | 5 | 已编译类的总体结构非常简单。实际上,与原生编译应用程序不同,已编译类中保留了来自源代码的结构信息和几乎所有符号。事实上,已编译类中包含如下各部分: 6 | - 专门一部分,描述类的修饰符(比如 public 和 private)、名字、超类、接口和注释。 7 | - 类中声明的每个字段各有一部分。每一部分描述一个字段的修饰符、名字、类型和注释。 8 | - 类中声明的每个方法及构造器各有一部分。每一部分描述一个方法的修饰符、名字、返回类型与参数类型、注释。它还以 Java 字节代码指令的形式,包含了该方法的已编译代码。 9 | 10 | **但在源文件类和已编译类之间还是有一些差异:** 11 | 12 | - 一个已编译类仅描述一个类,而一个源文件中可以包含几个类。比如,一个源文件描述了一个类,这个类又有一个内部类,那这个源文件会被编译为两个类文件:主类和内部类各一个文件。但是,主类文件中包含对其内部类的引用,定义了内部方法的内层类会包含引用,引向其封装的方法。 13 | - 已编译类中当然不包含注释(comment),但可以包含类、字段、方法和代码属性,可以利用这些属性为相应元素关联更多信息。Java 5 中引入可用于同一目的的注释 (annotaion)以后,属性已经变得没有什么用处了。 14 | - 编译类中不包含 package 和 import 部分,因此,所有类型名字都必须是完全限定的。另一个非常重要的结构性差异是已编译类中包含常量池(constant pool)部分。这个也是一个数组,其中包含了在类中出现的所有数值、字符串和类型常量。这些常量仅在这个常量池部分中定义一次,然后可以利用其索引,在类文件中的所有其他各部分进行引用。幸好,ASM 隐藏了与常量池有关的所有细节,所以我们不用再为它操心了。图 2.1 中总结了一个已编译类的整体结构。其确切结构在《Java 虚拟机规范》第 4 节中描述。 15 | 16 | | 已编译类的整体结构(*表示零个或多个)| 17 | |:---| 18 | | 修饰符、名字、超类、接口 | 19 | | 常量池:数值、字符串和类型常量 | 20 | | 源文件名(可选) | 21 | | 封装的类引用 | 22 | | 注释* | 23 | | 属性* | 24 | | 内部类* - 名称 | 25 | | 字段* 修饰符、名字、类型 | 26 | | 注释* | 27 | | 属性* | 28 | | 方法* 修饰符、名字、返回类型与参数类型 | 29 | | 注释* | 30 | | 属性* | 31 | | 编译后的代码 | 32 | 33 | *另一个重要的差别是 Java 类型在已编译类和源文件类中的表示不同。后面几节将解释它们在已编译类中的表示。* 34 | 35 | ## 2.1.2 内部名 36 | 37 | 在许多情况下,一种类型只能是类或接口类型。例如,一个类的超类、由一个类实现的接口, 或者由一个方法抛出的异常就不能是基元类型或数组类型,必须是类或接口类型。这些类型在已编译类中用内部名字表示。一个类的内部名就是这个类的完全限定名,其中的点号用斜线代替。例如,String 的内部名为 `java/lang/String`。 38 | 39 | ## 2.1.3 类型描述符 40 | 41 | 内部名只能用于类或接口类型。所有其他 Java 类型,比如字段类型,在已编译类中都是用**类型描述符**表示的(见图 2.2)。 42 | 43 | | Java 类型 | 类型描述符 | 44 | |:---|:---| 45 | | boolean | Z | 46 | | char | C | 47 | | byte | B | 48 | | short | S | 49 | | int | I | 50 | | float | F | 51 | | long | J | 52 | | double | D | 53 | | Object | Ljava/lang/Object; | 54 | | int[] | [I | 55 | | Object[][] | [[Ljava/lang/Object; | 56 | 57 | *图 2‐2 一些Java 类型的类型描述符* 58 | 59 | 基元类型的描述符是单个字符:Z 表示 boolean,C 表示 char,B 表示 byte,S 表示 short, I 表示 int,F 表示 float,J 表示 long,D 表示 double。一个类类型的描述符是这个类的内部名,前面加上字符 L ,后面跟有一个分号。例如, String 的类型描述符为 Ljava/lang/String;。而一个数组类型的描述符是一个方括号后面跟有该数组元素类型的描述符。 60 | 61 | ## 2.1.4 方法描述符 62 | 63 | **方法描述符**是一个类型描述符列表,它用一个字符串描述一个方法的参数类型和返回类型。方法描述符以左括号开头,然后是每个形参的类型描述符,然后是一个右括号,接下来是返回类型的类型描述符,如果该方法返回 void,则是 V(方法描述符中不包含方法的名字或参数名)。 64 | 65 | | 源文件中的方法声明 | 方法描述符 | 66 | |:---|:---| 67 | | void m(int i, float f) | (IF)V | 68 | | int m(Object o) | (Ljava/lang/Object;)I | 69 | | int[] m(int i, String s) | (ILjava/lang/String;)[I | 70 | | Object m(int[] i) | ([I)Ljava/lang/Object; | 71 | 72 | *图 2.3 方法描述符举例* 73 | 74 | 一旦知道了类型描述符如何工作,方法描述符的理解就容易了。例如,(I)I 描述一个方法, 它接受一个 int 类型的参数,返回一个 int。图 2.3 给出了几个方法描述符示例。 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/notes/2.3工具.md: -------------------------------------------------------------------------------- 1 | # 2.3 工具 2 | 3 | 除了 **ClassVisitor** 类和相关的 **ClassReader**、**ClassWriter** 组件之外,**ASM** 还在 ```org.objectweb.asm.util``` 包中提供了几个工具,这些工具在开发类生成器或适配器时可能非常有用,但在运行时不需要它们。**ASM** 还提供了一个实用类,用于在运行时处理内部名、类型描述符和方法描述符。所有这些工具都将在下面介绍。 4 | 5 | ## 2.3.1 Type 6 | 7 | 在前几节已经看到,**ASM API** 公开 **Java** 类型的形式就是它们在已编译类中的存储形式,也就是说,作为内部特性或类型描述符。也可以按照它们在源代码中的形式来公开它们,使代码更便于阅读。但这样就需要在 **ClassReader** 和 **ClassWriter** 中的两种表示形式之间进行系统转换,从而使性能降低。这就是为什么 **ASM** 没有透明地将内部名和类型描述符转换为它们等价的源代码形式。但它提供了 Type 类,可以在必要时进行手动转换。 8 | 9 | 一个 **Type** 对象表示一种 **Java** 类型,既可以由类型描述符构造,也可以由 **Class** 对象构建。 **Type** 类还包含表示基元类型的静态变量。例如,**Type.INT_TYPE** 是表示 **int** 类型的 **Type** 对象。 10 | 11 | ```getInternalName``` 方法返回一个 `Type` 的内部名。 例如, ```Type.getType(String.class). getInternalName()``` 给出 ```String``` 类的内部名,即 ```"java/lang/String"```。这一方法只能对类或接口类型使用。 12 | 13 | ```getDescriptor``` 方法返回一个 `Type` 的描述符。 比如,在代码中可以不使用 ```"Ljava/lang/String;"```,而是使用```Type.getType(String.class).getDescriptor()```。或者,可以不使用 ```I```,而是使用 ```Type.INT_TYPE.getDescriptor()```。 14 | 15 | `Type` 对象还可以表示方法类型。这种对象既可以从一个方法描述符构建,也可以由 **Method** 对象构建。 **getDescriptor** 方法返回与这一类型对应的方法描述符。此外, **getArgumentTypes** 和 **getReturnType** 方法可用于获取与一个方法的参数类型和返回类型相对应的 **Type** 对象。例如,```Type.getArgumentTypes("(I)V")```返回一个仅有一个元素 **Type.INT_TYPE** 的数组。与此类似,调用 ```Type.getReturnType("(I)V")``` 将返回 **Type.VOID_TYPE** 对象。 16 | 17 | ## 2.3.2 TraceClassVisitor 18 | 19 | 要确认所生成或转换后的类符合你的预期,**ClassWriter** 返回的字母数组并没有什么真正的用处,因为它对人类来说是不可读的。如果有文本表示形式,那使用起来就容易多了。这正是 **TraceClassVisitor** 类提供的东西。从名字可以看出,这个类扩展了 **ClassVisitor** 类, 并生成所访问类的文本表示。因此,我们不是用 **ClassWriter** 来生成类,而是使用 **TraceClassVisitor**,以获得关于实际所生成内容的一个可读轨迹。甚至可以同时使用这两者,这样要更好一些。除了其默认行为之外,**TraceClassVisitor** 实际上还可以将对其方法的所有调用委托给另一个访问器,比如 **ClassWriter**: 20 | 21 | ```java 22 | ClassWriter cw = new ClassWriter(0); 23 | TraceClassVisitor cv = new TraceClassVisitor(cw, printWriter); 24 | cv.visit(...); 25 | ... 26 | cv.visitEnd(); 27 | byte b[] = cw.toByteArray(); 28 | ``` 29 | 30 | 这一代码创建了一个 **TraceClassVisitor**,将它自己接收到的所有调用都委托给 **cw**,然后将这些调用的一份文本表示打印到 **printWriter**。例如,如果在 2.2.3 节的例子中使用 **TraceClassVisitor**,将会得出: 31 | 32 | ```java 33 | // 类版本号 49.0 (49) 34 | // 访问标志 1537 35 | public abstract interface pkg/Comparable implements pkg/Mesurable { 36 | // 访问标志 25 37 | public final static I LESS = -1 38 | 39 | //访问标志 25 40 | public final static I EQUAL = 0 41 | //访问标志 25 42 | public final static I GREATER = 1 43 | //访问标志 1025 44 | public abstract compareTo(Ljava/lang/Object;)I 45 | } 46 | ``` 47 | 48 | 注意,可以在生成链或转换链的任意位置使用 **TraceClassVisitor**,以查看在链中这一点发生了什么,并非一定要恰好在 **ClassWriter** 之前使用。还要注意,有了这个适配器生成的类的文本表示形式,可能很轻松地用 ```String.equals()``` 来对比两个类。 49 | 50 | ## 2.3.3 CheckClassAdapter 51 | 52 | **ClassWriter** 类并不会核实对其方法的调用顺序是否恰当,以及参数是否有效。因此,有可能会生成一些被 **Java** 虚拟机验证器拒绝的无效类。为了尽可能提前检测出部分此类错误,可以使用 **CheckClassAdapter** 类。和 **TraceClassVisitor** 类似,这个类也扩展了 **ClassVisitor** 类,并将对其方法的所有调用都委托到另一个 **ClassVisitor**,比如一个 **TraceClassVisitor** 或一个 **ClassWriter**。但是,这个类并不会打印所访问类的文本表示, 而是验证其对方法的调用顺序是否适当,参数是否有效,然后才会委托给下一个访问器。当发生错误时,会抛出 **IllegalStateException** 或 **IllegalArgumentException**。 53 | 54 | 为核对一个类,打印这个类的文本表示形式,最终创建一个字节数组表示形式,应当使用类似于如下代码: 55 | 56 | ```java 57 | ClassWriter cw = new ClassWriter(0); 58 | TraceClassVisitor tcv = new TraceClassVisitor(cw, printWriter); 59 | CheckClassAdapter cv = new CheckClassAdapter(tcv); cv.visit(...); 60 | ... 61 | cv.visitEnd(); 62 | byte b[] = cw.toByteArray(); 63 | ``` 64 | 65 | 注意,如果以不同顺序将这些类访问器链在一起,那它们执行的操作也将以不同顺序完成。例如,利用以下代码,这些核对工作将在轨迹之后进行: 66 | 67 | ```java 68 | ClassWriter cw = new ClassWriter(0); 69 | CheckClassAdapter cca = new CheckClassAdapter(cw); 70 | TraceClassVisitor cv = new TraceClassVisitor(cca, printWriter); 71 | ``` 72 | 73 | 和使用 **TraceClassVisitor** 时一样,也可以在一个生成链或转换链的任意位置使用 **CheckClassAdapter**,以查看该链中这一点的类,而不一定只是恰好在 **ClassWriter** 之前使用。 74 | 75 | ## 2.3.4 ASMifier 76 | 77 | 这个类为 **TraceClassVisitor** 工具提供了一种替代后端(该工具在默认情况下使用 **Textifier** 后端,生成如上所示类型的输出)。这个后端使 **TraceClassVisitor** 类的每个方 法都会打印用于调用它的 **Java** 代码。例如,调用 ```visitEnd()``` 方法将打印 ```cv.visitEnd();```。其结果是,当一个具有 **ASMifier** 后端的 **TraceClassVisitor** 访问器访问一个类时,它会打印用 **ASM** 生成这个类的源代码。如果用这个访问器来访问一个已经存在的类,那这一点是很有用的。例如,如果你不知道如何用 **ASM** 生成某个已编译类,可以编写相应的源代码,用 **javac** 编译它,并用 **ASMifier** 来访问这个编译后的类。将会得到生成这个已编译类的 **ASM** 代码! **ASMifier** 类也可以在命令行中使用。例如,使用以下命令; 78 | 79 | ```java 80 | java -classpath asm.jar:asm-util.jar \ org.objectweb.asm.util.ASMifier \ java.lang.Runnable 81 | ``` 82 | 83 | 将会生成一些代码,经过缩进后,这些代码就是如下模样: 84 | 85 | ```java 86 | package asm.java.lang; 87 | 88 | import org.objectweb.asm.*; 89 | 90 | public class RunnableDump implements Opcodes { 91 | public static byte[] dump() throws Exception { 92 | ClassWriter cw = new ClassWriter(0); 93 | FieldVisitor fv; 94 | MethodVisitor mv; 95 | AnnotationVisitor av0; 96 | cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, 97 | "java/lang/Runnable", null, "java/lang/Object", null); 98 | { 99 | mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "run", "()V", null, null); 100 | mv.visitEnd(); 101 | } 102 | cw.visitEnd(); 103 | return cw.toByteArray(); 104 | } 105 | } 106 | ``` 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /docs/notes/3.0方法.md: -------------------------------------------------------------------------------- 1 | 本章解释如何用核心 **ASM API** 生成和转换已编译方法。首先介绍编译后的方法,然后介绍用于生成和转换它们的相应 **ASM** 接口、组件和工具,并给出大量说明性示例。 -------------------------------------------------------------------------------- /docs/notes/3.1结构.md: -------------------------------------------------------------------------------- 1 | # 3.1 结构 2 | 3 | 在编译类的内部,方法的代码存储为一系列的**字节码**指令。为生成和转换类,最根本的就是要了解这些指令,并理解它们是如何工作的。本节将对这些指令进行全面概述,这些内容足以开始编写简单的类生成器与转换器代码。如需完整定义,应当阅读 Java 虚拟机规范。 4 | 5 | ## 3.1.1 执行模型 6 | 7 | 在介绍字节代码指令之前,有必要先来介绍 Java 虚拟机执行模型。我们知道,Java 代码是在**线程**内部执行的。每个线程都有自己的执行栈,栈由**帧**组成。每个帧表示一个方法调用:每次调用一个方法时,会将一个新帧压入当前线程的执行栈。当方法返回时,或者是正常返回,或者是因为异常返回,会将这个帧从执行栈中弹出,执行过程在发出调用的方法中继续进行(这个方法的帧现在位于栈的顶端)。 8 | 9 | 每一帧包括两部分:一个局部变量部分和一个操作数栈部分。**局部变量**部分包含可根据索引以随机顺序访问的变量。由名字可以看出,**操作数栈**部分是一个栈,其中包含了供字节代码指令用作操作数的值。这意味着这个栈中的值只能按照“后入先出”顺序访问。不要将操作数栈和线程的执行栈相混淆:执行栈中的每一帧都包含**自己**的操作数栈。 10 | 11 | 局部变量部分与操作数栈部分的大小取决于方法的代码。这一大小是在编译时计算的,并随字节代码指令一起存储在已编译类中。因此,对于对应于某一给定方法调用的所有帧,其局部变量与操作数栈部分的大小相同,但对应于不同方法的帧,这一大小可能不同。 12 | 13 | ![](http://asm.itstack.org/assets/img/2020/3.1-1.png) 14 | 15 | 给出了一个具有 3 帧的示例执行栈。第一帧包含 3 个局部变量,其操作数栈的最大值为 4,其中包含两个值。第二帧包含 2 个局部变量,操作数栈中有两个值。最后是第三帧,位于执行栈的顶端,包含 4 个局部变量和两个操作数。 16 | 17 | 在创建一个帧时,会将其初始化,提供一个空栈,并用目标对象 this(对于非静态方法) 及该方法的参数来初始化其局部变量。例如,调用方法 a.equals(b)将创建一帧,它有一个空栈,前两个局部变量被初始化为 a 和 b(其他局部变量未被初始化)。 18 | 19 | 局部变量部分和操作数栈部分中的每个槽 **(slot)** 可以保存除 **long** 和 **double** 变量之外的任意 **Java** 值。**long** 和 **double** 变量需要两个槽。这使局部变量的管理变得复杂:例如,第 i 个方法参数不一定存储在局部变量 i 中。例如,调用 Math.max(1L, 2L)创建一个帧,1L 值位于前两个局部变量槽中,值 2L 存储在第三和第四个槽中。 20 | 21 | ## 3.1.2 字节代码指令 22 | 23 | 字节代码指令由一个标识该指令的操作码和固定数目的参数组成: 24 | 25 | - **操作码**是一个无符号字节值——即字节代码名,由助记符号标识。例如,操作码 0 用助记符号 NOP 表示,对应于不做任何操作的指令。 26 | - **参数**是静态值,确定了精确的指令行为。它们紧跟在操作码之后给出。比如 GOTO 标记指令(其操作码的值为 167)以一个指明下一条待执行指令的标记作为参数标记。不要将指令参数与指令操作数相混淆:参数值是静态已知的,存储在编译后的代码中,而操作数值来自操作数栈,只有到运行时才能知道。 27 | 28 | 字节代码指令可以分为两类:一小组指令,设计用来在局部变量和操作数栈之间传送值;其他一些指令仅用于操作数栈:它们从栈中弹出一些值,根据这些值计算一个结果,并将它压回栈中。 29 | 30 | ```ILOAD```, ```LLOAD```, ```FLOAD```, ```DLOAD``` 和 ```ALOAD``` 指令读取一个局部变量,并将它的值压到操作数栈中。它们的参数是必须读取的局部变量的索引 **i**。```ILOAD``` 用于加载一个 ```boolean```、```byte```、 ```char```、```short``` 或```int``` 局部变量。```LLOAD```、```FLOAD``` 和```DLOAD``` 分别用于加载```long```、```float``` 或```double``` 值。(```LLOAD``` 和 ```DLOAD``` 实际加载两个槽 ```i``` 和 ```i+1```)。最后,```ALOAD``` 用于加载任意非基元值,即对象和数组引用。与之对应,```ISTORE```、```LSTORE```、```FSTORE```、```DSTORE``` 和 ```ASTORE``` 指令从操作数栈中弹出一个值,并将它存储在由其索引 `i` 指定的局部变量中。 31 | 32 | 可以看到,```xLOAD``` 和 ```xSTORE``` 指令被赋入了类型(事实上,下面将要看出,几乎所有指令都被赋予了类型)。它用于确保不会执行非法转换。实际上,将一个值存储在局部变量中,然后再以不同类型加载它,是非法的。例如,```ISTORE 1```、 ```ALOAD 1``` 序列是非法的——它允许将一个 任意内存位置存储在局部变量 1 中,并将这个地址转换为对象引用!但是,如果向一个局部变量中存储一个值,而这个值的类型不同于该局部变量中存储的当前值,却是完全合法的。这意味着一个局部变量的类型,即这个局部变量中所存值的类型可以在方法执行期间发生变化。 33 | 34 | 上面已经说过,所有其他字节代码指令都仅对操作数栈有效。它们可以划分为以下类别(见附件 A.1): 35 | 36 | | 内容 | 描述 | 37 | |:---|:---| 38 | | 栈 | 这些指令用于处理栈上的值:POP 弹出栈顶部的值,DUP 压入顶部栈值的一个副本, SWAP 弹出两个值,并按逆序压入它们,等等。 | 39 | | 常量 | 这些指令在操作数栈压入一个常量值:ACONST_NULL 压入 null,ICONST_0 压入 int 值 0,FCONST_0 压入 0f,DCONST_0 压入 0d,BIPUSH b 压入字节值 b,SIPUSH s 压入 short 值 s,LDC cst 压入任意 int、float、long、double、String 或 class① 常量 cst,等等。 | 40 | | 算术与逻辑 | 这些指令从操作数栈弹出数值,合并它们,并将结果压入栈中。它们没有任何参数。xADD、xSUB、xMUL、xDIV 和 xREM 对应于+、-、*、/和%运算,其中 x 为 I、 L、F 或 D 之一。类似地,还有其他对应于<<、>>、>>>、与、&和^运算的指令,用于处理 int 和 long 值。 | 41 | | 类型变换 | 这些指令从栈中弹出一个值,将其转换为另一类型,并将结果压入栈中。它们对应于 Java 中的类型转换表达式。I2F, F2D, L2D 等将数值由一种数值类型转换为另一种类型。CHECKCAST t 将一个引用值转换为类型 t。 | 42 | | 对象 | 这些指令用于创建对象、锁定它们、检测它们的类型,等等。例如,NEW type 指令将一个 type 类型的新对象压入栈中(其中 type 是一个内部名)。 | 43 | | 字段 | 这些指令读或写一个字段的值。GETFIELD owner name desc 弹出一个对象引用,并压和其 name 字段中的值。PUTFIELD owner name desc 弹出一个值和一个对象引用,并将这个值存储在它的 name 字段中。在这两种情况下,该对象都必须是 owner 类型,它的字段必须为 desc 类型。GETSTATIC 和 PUTSTATIC 是类似指令,但用于静态字段。 | 44 | | 方法 | 这些指令调用一个方法或一个构造器。它们弹出值的个数等于其方法参数个数加 1 (用于目标对象),并压回方法调用的结果。INVOKEVIRTUAL owner name desc 调用在类 owner 中定义的 name 方法,其方法描述符为 desc。INVOKESTATIC 用于静态方法, INVOKESPECIAL 用于私有方法和构造器,INVOKEINTERFACE 用于接口中定义的方法。最后,对于 Java 7 中的类,INVOKEDYNAMIC 用于新动态方法调用机制。 | 45 | | 数组 | 这些指令用于读写数组中的值。xALOAD 指令弹出一个索引和一个数组,并压入此索引处数组元素的值。xASTORE 指令弹出一个值、一个索引和一个数组,并将这个值存储在该数组的这一索引处。这里的 x 可以是 I、L、F、D 或 A,还可以是 B、C 或 S。 | 46 | | 跳转 | 这些指令无条件地或者在某一条件为真时跳转到一条任意指令。它们用于编译 if、 for、do、while、break 和 continue 指令。例如,IFEQ label 从栈中弹出一个 int 值,如果这个值为 0,则跳转到由这个 label 指定的指令处(否则,正常执行下一条指令)。还有许多其他跳转指令,比如 IFNE 或 IFGE。最后,TABLESWITCH 和LOOKUPSWITCH 对应于 switch Java 指令。 | 47 | | 返回 | 最后,xRETURN 和 RETURN 指令用于终止一个方法的执行,并将其结果返回给调用者。RETURN 用于返回 void 的方法,xRETURN 用于其他方法。 | 48 | 49 | ## 3.1.3 示例 50 | 51 | 让我们看一些基本示例,具体体会一下字节代码指令是如何工作的。考虑下面的 bean 类: 52 | 53 | ```java 54 | package pkg; 55 | 56 | public class Bean { 57 | private int f; 58 | 59 | public int getF() { 60 | return this.f; 61 | } 62 | 63 | public void setF(int f) { 64 | this.f = f; 65 | } 66 | } 67 | ``` 68 | 69 | getter 方法的字节代码为: 70 | 71 | ```java 72 | ALOAD 0 73 | GETFIELD pkg/Bean f I IRETURN 74 | ``` 75 | 76 | 第一条指令读取局部变量 0(它在为这个方法调用创建帧期间被初始化为 this),并将这个值压入操作数栈中。第二个指令从栈中弹出这个值,即 this,并将这个对象的 f 字段压入栈中, 即 this.f。最后一条指令从栈中弹出这个值,并将其返回给调用者。图 3.2 中给出了这个方法执行帧的持续状态。 77 | 78 | ![](http://asm.itstack.org/assets/img/2020/3.1-2.png) 79 | 80 | getF 方法的持续帧状态:a) 初始状态,b) 在 ALOAD 0 之后,c) 在 GETFIELD 之后 81 | 82 | setter 方法的字节代码: 83 | 84 | ```java 85 | ALOAD 0 86 | ILOAD 1 87 | PUTFIELD pkg/Bean f I RETURN 88 | ``` 89 | 90 | 和之前一样,第一条指令将 this 压入操作数栈。第二条指令压入局部变量 1,在为这个方法调用创建帧期间,以 f 参数初始化该变量。第三条指令弹出这两个值,并将 int 值存储在被引用对象的 f 字段中,即存储在 this.f 中。最后一条指令在源代码中是隐式的,但在编译后的代码中却是强制的,销毁当前执行帧,并返回调用者。这个方法执行帧的持续状态如图 3.3 所示。 91 | 92 | ![](http://asm.itstack.org/assets/img/2020/3.1-3.png) 93 | 94 | setF 方法的持续状态:a) 初始状态,b) 在 ALOAD 0 之后,c)在 ILOAD 1 之后,d) 在 PUTFIELD 之后 95 | 96 | Bean 类还有一个默认的公有构造器,由于程序员没有定义显式的构造器,所以它是由编译器生成的。这个默认的公有构造器被生成为 Bean() { super(); }。这个构造器的字节代码如下: 97 | 98 | ```java 99 | ALOAD 0 100 | INVOKESPECIAL java/lang/Object ()V RETURN 101 | ``` 102 | 103 | 第一条指令将 this 压入操作数栈中。第二条指令从栈中弹出这个值,并调用在 Object 对象中定义的方法。这对应于 super()调用,也就是对超类 Object 构造器的调用。在这里可以看到,在已编译类和源类中对构造器的命名是不同的:在编译类中,它们总是被命名为,而在源类中,它们的名字与定义它们的类同名。最后一条指令返回调用者。 104 | 105 | 现在让我们考虑一个稍为复杂一点的 setter 方法: 106 | 107 | ```java 108 | public void checkAndSetF(int f) { 109 | if (f >= 0) { 110 | this.f = f; 111 | } else { 112 | throw new IllegalArgumentException(); 113 | } 114 | } 115 | ``` 116 | 117 | 这个新 setter 方法的字节代码如下: 118 | 119 | ```java 120 | ILOAD 1 121 | IFLT label 122 | ALOAD 0 123 | ILOAD 1 124 | PUTFIELD pkg/Bean f I GOTO end 125 | label: 126 | NEW java/lang/IllegalArgumentException DUP 127 | INVOKESPECIAL java/lang/IllegalArgumentException ()V ATHROW 128 | end: 129 | RETURN 130 | ``` 131 | 132 | 第一条指令将初始化为 f 的局部变量 1 压入操作数栈。IFLT 指令从栈中弹出这个值,并将它与 0 进行比较。如果它小于(LT)0,则跳转到由 label 标记指定的指令,否则不做任何事情,继续执行下一条指令。接下来的三条指令与 setF 方法中相同。GOTO 指令无条件跳转到由 end 标记指定的指令,也就是 RETURN 指令。label 和 end 标记之间的指令创建和抛出一个异常:NEW 指令创建一个异常对象,并将它压入操作数栈中。DUP 指令在栈中重复这个值。 INVOKESPECIAL 指令弹出这两个副本之一,并对其调用异常构造器。最后,ATHROW 指令弹出剩下的副本,并将它作为异常抛出(所以不会继续执行下一条指令)。 133 | 134 | ## 3.1.4 异常处理器 135 | 136 | 不存在用于捕获异常的字节代码:而是将一个方法的字节代码与一个**异常处理器**列表关联在一起,这个列表规定了在某方法中一给定部分抛出异常时必须执行的代码。异常处理器类似于try catch 块:它有一个范围,也就是与 try 代码块内容相对应的一个指令序列,还有一个处理器,对应于 catch 块中的内容。这个范围由一个起始标记和一个终止标记指定,处理器由一个起始标记指定。比如下面的源代码: 137 | 138 | ```java 139 | public static void sleep(long d) { 140 | try { 141 | Thread.sleep(d); 142 | } catch (InterruptedException e) { 143 | e.printStackTrace(); 144 | } 145 | } 146 | ``` 147 | 148 | 可被编译为: 149 | 150 | ```java 151 | TRYCATCHBLOCK try catch catch java/lang/InterruptedException 152 | try: 153 | LLOAD 0 154 | INVOKESTATIC java/lang/Thread sleep (J)V RETURN 155 | catch: 156 | INVOKEVIRTUAL java/lang/InterruptedException printStackTrace ()V RETURN 157 | ``` 158 | 159 | Try 和 catch 标记之间的代码对应于 try 块,而 catch 标记之后的代码对应于 catch。 TRYCATCHBLOCK 行指定了一个异常处理器,覆盖了 try 和 catch 标记之间的范围,有一个开始于 catch 标记的处理器,用于处理一些异常,这些异常的类是 InterruptedException 的子类。这意味着,如果在 try 和 catch 之间抛出了这样一个异常,栈将被清空,异常被压入这个空栈中,执行过程在 catch 处继续。 160 | 161 | ## 3.1.5 帧 162 | 163 | 除了字节代码指令之外,用 Java 6 或更高版本编译的类中还包含一组**栈映射帧**,用于加快 Java 虚拟机中类验证过程的速度。栈映射帧给出一个方法的执行帧在执行过程中某一时刻的状态。更准确地说,它给出了在就要执行某一特定字节代码指令之前,每个局部变量槽和每个操作数栈槽中包含的值的类型。 164 | 165 | 例如,如果考虑上一节的 getF 方法,可以定义三个栈映射帧,给出执行帧在即将执行 ALOAD、即将执行 GETFIELD 和即将执行 IRETURN 之前的状态。这三个栈映射帧对应于图 3.2 给出的三种情况,可描述如下,其中第一个方括号中的类型对应于局部变量,其他类型对应于操作数栈: 166 | 167 | | 如下代码之前的执行帧状态 | 指令 | 168 | |:---|:---| 169 | | [pkg/Bean] [] | ALOAD 0 | 170 | | [pkg/Bean] [pkg/Bean] | GETFIELD | 171 | | [pkg/Bean] [I] | IRETURN | 172 | 173 | | 如下代码之前的执行帧状态 | 指令 | 174 | |:---|:---| 175 | | [pkg/Bean I] [] | ILOAD 1 | 176 | | [pkg/Bean I] [I] | IFLT label | 177 | | [pkg/Bean I] [] | ALOAD 0 | 178 | | [pkg/Bean I] [pkg/Bean] | ILOAD 1 | 179 | | [pkg/Bean I] [pkg/Bean I] | PUTFIELD | 180 | | [pkg/Bean I] [] | GOTO end | 181 | | [pkg/Bean I] [] | label : | 182 | | [pkg/Bean I] [] | NEW | 183 | | [pkg/Bean I] [未初始化(标记)] | DUP | 184 | | [pkg/Bean I] [Uninitialized(label) | INVOKESPECIAL | 185 | | Uninitialized(label)] | - | 186 | | [pkg/Bean I] | ATHROW | 187 | | [java/lang/IllegalArgumentException] | - | 188 | | [pkg/Bean I] []| end : | 189 | | [pkg/Bean I] []| RETURN | 190 | 191 | 除了 **Uninitialized(label)** 类型之外,它与前面的方法均类似。这是一种仅在栈映射帧中使用的特殊类型,它指定了一个对象,已经为其分配了内存,但还没有调用其构造器。参数规定了创建此对象的指令。对于这个类型的值,只能调用一种方法,那就是构造器。在调用它时, 在帧中出现的所有这一类型都被代以一个实际类型,这里是 **IllegalArgumentException**。栈映射帧可使用三种其他特殊类型:UNINITIALIZED_THIS 是构造器中局部变量 0 的初始类型,TOP 对应于一个未定义的值,而 NULL 对应于 null。 192 | 193 | 上文曾经说过,从 Java 6 开始,除了字节代码之外,已编译类中还包含了一组栈映射帧。为节省空间,已编译方法中并没有为每条指令包含一个帧:事实上,它仅为那些对应于跳转目标或异常处理器的指令,或者跟在无条件跳转指令之后的指令包含帧。事实上,可以轻松、快速地由这些帧推断出其他帧。 194 | 195 | 在 checkAndSetF 方法的情景中,这意味着仅存储两个帧:一个用于 NEW 指令,因为它是 IFLT 指令的目标,还因为它跟在无条件跳转 GOTO 指令之后,另一个用于 **RETURN** 指令,因为它是 GOTO 指令的目标,还因为它跟在“无条件跳转”**ATHROW** 指令之后。 196 | 197 | 为节省更多空间,对每一帧都进行压缩:仅存储它与前一帧的差别,而初始帧根本不用存储, 可以轻松地由方法参数类型推导得出。在 checkAndSetF 方法中,必须存储的两帧是相同的, 都等于初始帧,所以它们被存储为单字节值,由 F_SAME 助记符表示。可以在与这些帧相关联的字节代码指令之前给出这些帧。这就给出了 F_SAME 方法的最终字节代码: 198 | 199 | ```java 200 | ILOAD 1 201 | IFLT label 202 | ALOAD 0 203 | ILOAD 1 204 | PUTFIELD pkg/Bean f I GOTO end 205 | label: 206 | F_SAME 207 | NEW java/lang/IllegalArgumentException DUP 208 | INVOKESPECIAL java/lang/IllegalArgumentException ()V ATHROW 209 | end: 210 | F_SAME 211 | RETURN 212 | ``` 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /docs/notes/3.3工具.md: -------------------------------------------------------------------------------- 1 | # 3.3 工具 2 | 3 | `org.objectweb.asm.commons` 包中包含了一些预定义的方法适配器,可用于定义我们自己的适配器。这一节将介绍其中的三个,并用 3.2.4 节的 **AddTimerAdapter** 示例说明如何使用它们。我们还说说明,如何利用上一章看到的工具来简化方法生成或转换。 4 | 5 | ## 3.3.1 基本工具 6 | 7 | 2.3 节介绍的工具也可用于方法。 8 | 9 | 1. Type 10 | 许多字节代码指令,比如 ```xLOAD```、```xADD``` 或 ```xRETURN``` 依赖于将它们应用于哪种类型。Type 类提供了一个 getOpcode 方法,可用于为这些指令获取与一给定类型相对应的操作码。这一方法的参数是一个 int 类型的操作码,针对哪种类型调用该方法,则返回该哪种类型的操作码。例如 **t.getOpcode(IMUL)**,若 t 等于 **Type.FLOAT_TYPE**,则返回 **FMUL**。 11 | 12 | 2. TraceClassVisitor 13 | 这个类在上一章已经介绍过,它打印它所访问类的文本表示,包括类的方法的文本表示,其方式非常类似于这一章使用的方式。因此,可以将它用来跟踪在一个转换链中任意点处所生成或所转换方法的内容。例如: 14 | 15 | ```java 16 | java -classpath asm.jar:asm-util.jar \ 17 | org.objectweb.asm.util.TraceClassVisitor \ 18 | java.lang.Void 19 | ``` 20 | 21 | 将输出: 22 | 23 | ```java 24 | // class version 49.0 (49) 25 | // access flags 49 26 | public final class java/lang/Void { 27 | // access flags 25 28 | // signature Ljava/lang/Class; 29 | // declaration: java.lang.Class public final static Ljava/lang/Class; TYPE 30 | // access flags 2 private ()V 31 | ALOAD 0 32 | INVOKESPECIAL java/lang/Object. ()V RETURN 33 | MAXSTACK = 1 34 | MAXLOCALS = 1 35 | // access flags 8 static ()V 36 | LDC "void" 37 | INVOKESTATIC java/lang/Class.getPrimitiveClass (...)... PUTSTATIC java/lang/Void.TYPE : Ljava/lang/Class; RETURN 38 | MAXSTACK = 1 39 | MAXLOCALS = 0 40 | } 41 | ``` 42 | 43 | 它说明如何生成一个静态块 static { ... },也就是用方法(用于 CLass INITializer)。注意,如果希望跟踪某一个方法在链中某一点处的内容,而不是跟踪类的所有内容,可以用 TraceMethodVisitor 代替 TraceClassVisitor(在这种情况下,必须显式指定后端;这里使用了一个 Textifier): 44 | 45 | ```java 46 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 47 | MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); 48 | if (debug && mv != null && ...){ // 如果必须跟踪此方法 49 | Printer p = new Textifier(ASM4) { 50 | @Override 51 | public void visitMethodEnd() { 52 | print(aPrintWriter); // 在其被访问后输出它 53 | } 54 | }; 55 | mv = new TraceMethodVisitor(mv, p); 56 | } 57 | return new MyMethodAdapter(mv); 58 | } 59 | ``` 60 | 61 | 这一代码输出该方法经 MyMethodAdapter 转换过后的结果。 62 | 63 | 3. CheckClassAdapter 64 | 65 | 这个类也已经在上一章介绍过,它检查 ClassVisitor 方法的调用顺序是否适当,参数是否有效,所做的工作与 MethodVisitor 方法相同。因此,可用于检查 MethodVisitor API 在一个转换链中任意点的使用是否正常。和 TraceMethodVisitor 类似, 可以用CheckMethodAdapter 类来检查一个方法,而不是检查它的整个类: 66 | 67 | ```java 68 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 69 | MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); 70 | if (debug && mv != null && ...){ // 如果必须检查这个方法 71 | mv = new CheckMethodAdapter(mv); 72 | } 73 | return new MyMethodAdapter(mv); 74 | } 75 | ``` 76 | 77 | 这一代码验证 MyMethodAdapter 正确地使用了 MethodVisitor API。但要注意,这一适配器并没有验证字节代码是正确的:例如,它没有检测出 ISTORE 1 ALOAD 1 是无效的。实际上,如果使用 CheckMethodAdapter 的其他构造器(见 Javadoc),并且在 visitMaxs中提供有效的 maxStack 和 maxLocals 参数,那这种错误是可以被检测出来的。 78 | 79 | 4. ASMifier 80 | 这个类已经在上一章介绍过,也用于处理方法的内容。利用它,可以知道如何用 ASM 生成一些编译后的代码:只需要用 Java 编写相应的源代码,用 javac 编译它,然后用 **ASMifier** 访问这个类。你会得到 ASM 代码,以生成与源代码相对应的字节代码。 81 | 82 | ## 3.3.2 AnalyzerAdapter 83 | 84 | 这个方法适配器根据 visitFrame 中访问的帧,计算每条指令之前的栈映射帧。实际上, 如 3.1.5 节中的解释,visitFrame 仅在方法中的一些特定指令前调用,一方面是为了节省空间, 另一方面也是因为“其他帧可以轻松快速地由这些帧推导得出”。这就是这个适配器所做的工作。当然,它仅对那些包含预计算栈映射帧的类有效,也就是对于用 Java 6 或更高版本编译的有效(或者用一个使用 COMPUTE_FRAMES 选项的 ASM 适配器升级到 Java 6)。 85 | 86 | 在我们的 AddTimerAdapter 示例中,这个适配器可用于获得操作数栈恰在 RETURN 指令之前的大小,从而允许为 visitMaxs 中的 maxStack 计算一个最优的已转换值(事实上,在实践中并不建议使用这一方法,因为它的效率要远低于使用 COMPUTE_MAXS): 87 | 88 | ```java 89 | class AddTimerMethodAdapter2 extends AnalyzerAdapter { 90 | private int maxStack; 91 | 92 | public AddTimerMethodAdapter2(String owner, int access, String name, String desc, MethodVisitor mv) { 93 | super(ASM4, owner, access, name, desc, mv); 94 | } 95 | 96 | @Override 97 | public void visitCode() { 98 | super.visitCode(); 99 | mv.visitFieldInsn(GETSTATIC, owner, "timer", "J"); 100 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J"); 101 | mv.visitInsn(LSUB); 102 | mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); 103 | maxStack = 4; 104 | } 105 | 106 | @Override 107 | public void visitInsn(int opcode) { 108 | if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) { 109 | 110 | mv.visitFieldInsn(GETSTATIC, owner, "timer", "J"); 111 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", 112 | "currentTimeMillis", "()J"); 113 | mv.visitInsn(LADD); 114 | mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); 115 | maxStack = Math.max(maxStack, stack.size() + 4); 116 | } 117 | super.visitInsn(opcode); 118 | } 119 | 120 | @Override 121 | public void visitMaxs(int maxStack, int maxLocals) { 122 | super.visitMaxs(Math.max(this.maxStack, maxStack), maxLocals); 123 | } 124 | } 125 | ``` 126 | 127 | stack 字段在 AnalyzerAdapter 类中定义,包含操作数栈中的类型。更准确地说,在一个 visitXxx Insn 中,且在调用被重写的方法之前,它会列出操作数栈正好在这条指令之前的状态。注意,必须调用被重写的方法,使 stack 字段被正确更新(因此,用 super 代替源代码中的 mv)。 128 | 129 | 或者,也可以通过调用超类中的方法来插入新指令:其方法就是这些指令的帧将由 AnalyzerAdapter 计算,由于这个适配器会根据它计算的帧来更新 visitMaxs 的参数,所以我们不需要自己来更新它们: 130 | 131 | ```java 132 | class AddTimerMethodAdapter3 extends AnalyzerAdapter { 133 | public AddTimerMethodAdapter3(String owner, int access, 134 | String name, String desc, MethodVisitor mv) { 135 | super(ASM4, owner, access, name, desc, mv); 136 | } 137 | 138 | @Override 139 | public void visitCode() { 140 | super.visitCode(); 141 | super.visitFieldInsn(GETSTATIC, owner, "timer", "J"); 142 | super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J"); 143 | super.visitInsn(LSUB); 144 | super.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); 145 | } 146 | 147 | @Override 148 | public void visitInsn(int opcode) { 149 | if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) { 150 | super.visitFieldInsn(GETSTATIC, owner, "timer", "J"); 151 | super.visitMethodInsn(INVOKESTATIC, "java/lang/System", 152 | "currentTimeMillis", "()J"); 153 | super.visitInsn(LADD); 154 | super.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); 155 | } 156 | super.visitInsn(opcode); 157 | } 158 | } 159 | ``` 160 | 161 | ## 3.3.3 LocalVariablesSorter 162 | 163 | 这个方法适配器将一个方法中使用的局部变量按照它们在这个方法中的出现顺序重新进行编号。例如,在一个有两个参数的方法中,第一个被读取或写入且索引大于或等于 3 的局部变量 (前三个局部变量对应于 this 及两个方法参数,因此不会发生变化)被赋予索引 3,第二个被赋予索引 4,以此类推。在向一个方法中插入新的局部变量时,这个适配器很有用。没有这个适配 器,就需要在所有已有局部变量之后添加新的局部变量,但遗憾的是,在 visitMaxs 中,要直到方法的末尾处才能知道这些局部变量的编号。 164 | 165 | 为说明如何使用这个适配器,假定我们希望使用一个局部变量来实现 AddTimerAdapter: 166 | 167 | ```java 168 | public class C { 169 | public static long timer; 170 | 171 | public void m() throws Exception { 172 | long t = System.currentTimeMillis(); 173 | Thread.sleep(100); 174 | timer += System.currentTimeMillis() - t; 175 | } 176 | } 177 | ``` 178 | 179 | 这一点很容易做到:只需扩展 LocalVariablesSorter ,并使用这个类中定义的 newLocal 方法。 180 | 181 | ```java 182 | class AddTimerMethodAdapter4 extends LocalVariablesSorter { 183 | private int time; 184 | 185 | public AddTimerMethodAdapter4(int access, String desc, MethodVisitor mv) { 186 | super(ASM4, access, desc, mv); 187 | } 188 | 189 | @Override 190 | public void visitCode() { 191 | super.visitCode(); 192 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J"); 193 | time = newLocal(Type.LONG_TYPE); 194 | mv.visitVarInsn(LSTORE, time); 195 | } 196 | 197 | @Override 198 | public void visitInsn(int opcode) { 199 | if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) { 200 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", 201 | "currentTimeMillis", "()J"); 202 | mv.visitVarInsn(LLOAD, time); 203 | mv.visitInsn(LSUB); 204 | mv.visitFieldInsn(GETSTATIC, owner, "timer", "J"); 205 | mv.visitInsn(LADD); 206 | mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); 207 | } 208 | super.visitInsn(opcode); 209 | } 210 | 211 | @Override 212 | public void visitMaxs(int maxStack, int maxLocals) { 213 | super.visitMaxs(maxStack + 4, maxLocals); 214 | } 215 | } 216 | ``` 217 | 218 | 注意,在对局部变量重新编号后,与该方法相关联的原帧变为无效,在插入新局部变量后更不必说了。幸好,还是可能避免从头重新计算这些帧的:事实上,并不存在必须添加或删除的帧, 只需对原帧中局部变量的内容进行重新排序, 为转换后的方法获得帧就“足够” 了。 LocalVariablesSorter 会自动负责完成。如果还需要为你的方法适配器进行增量栈映射帧更新,可以由这个类的源代码中获得灵感。 219 | 220 | 前面曾经说过,这个类的原版本中存在关于最糟情景下 maxStack 取值的问题,在上面可以看出,使用局部变量并不能解决这个问题。如果希望用 AnalyzerAdapter 解决这个问题, 除了 LocalVariablesSorter 之外,必须通过委托使用这些适配器,而不是通过继承(因为不可能存在多个继承): 221 | 222 | ```java 223 | class AddTimerMethodAdapter5 extends MethodVisitor { 224 | public LocalVariablesSorter lvs; 225 | public AnalyzerAdapter aa; 226 | private int time; 227 | private int maxStack; 228 | 229 | public AddTimerMethodAdapter5(MethodVisitor mv) { 230 | super(ASM4, mv); 231 | } 232 | 233 | @Override 234 | public void visitCode() { 235 | mv.visitCode(); 236 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J"); 237 | time = lvs.newLocal(Type.LONG_TYPE); 238 | mv.visitVarInsn(LSTORE, time); 239 | maxStack = 4; 240 | } 241 | 242 | @Override 243 | public void visitInsn(int opcode) { 244 | if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) { 245 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", 246 | "currentTimeMillis", "()J"); 247 | mv.visitVarInsn(LLOAD, time); 248 | mv.visitInsn(LSUB); 249 | mv.visitFieldInsn(GETSTATIC, owner, "timer", "J"); 250 | mv.visitInsn(LADD); 251 | mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); 252 | maxStack = Math.max(aa.stack.size() + 4, maxStack); 253 | } 254 | mv.visitInsn(opcode); 255 | } 256 | 257 | @Override 258 | public void visitMaxs(int maxStack, int maxLocals) { 259 | mv.visitMaxs(Math.max(this.maxStack, maxStack), maxLocals); 260 | } 261 | } 262 | ``` 263 | 264 | 为使用这 个适配器 ,必须将 一个 LocalVariablesSorter 链接到一 个 AnalyzerAdapter,再将它自身连接到你的适配器:第一个适配器将对局部变量排序,并相应地更新帧,分析适配器将计算中间帧,在此过程中会考虑上一个适配器中完成的重新编号,你的适配器将可以访问这些重新编号的中间帧。这个链接可以在 visitMethod 中构造如下: 265 | 266 | ```java 267 | mv=cv.visitMethod(access,name,desc,signature,exceptions); 268 | if(!isInterface && mv!=null && !name.equals("")){ 269 | AddTimerMethodAdapter5 at = new AddTimerMethodAdapter5(mv); 270 | at.aa = new AnalyzerAdapter(owner,access,name,desc,at); 271 | at.lvs = new LocalVariablesSorter(access,desc,at.aa); 272 | return at.lvs; 273 | } 274 | ``` 275 | 276 | ## 3.3.4 AdviceAdapter 277 | 278 | 这个方法适配器是一个抽象类,可用于在一个方法的开头以及恰在任意 RETURN 或 ATHROW 指令之前插入代码。它的主要好处就是对于构造器也是有效的,在构造器中,不能将代码恰好插入到构造器的开头,而是插在对超构造器的调用之后。事实上,这个适配器的大多数代码都专门用于检测对这个超构造器的调用。 279 | 280 | 仔细研究 3.2.4 节中的 AddTimerAdapter 类将会看到,AddTimerMethodAdapter 因为这一原因而未被用于构造器。这一方法适配器从 AdviceAdapter 继承而来,可以对其进行改 进,以便对于构造器同样有效(注意,AdviceAdapter 继承自 LocalVariablesSorter, 所以也可以轻松使用一个局部变量): 281 | 282 | ```java 283 | class AddTimerMethodAdapter6 extends AdviceAdapter { 284 | public AddTimerMethodAdapter6(int access, String name, String desc, MethodVisitor mv) { 285 | super(ASM4, mv, access, name, desc); 286 | } 287 | 288 | @Override 289 | protected void onMethodEnter() { 290 | mv.visitFieldInsn(GETSTATIC, owner, "timer", "J"); 291 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", 292 | "currentTimeMillis", "()J"); 293 | mv.visitInsn(LSUB); 294 | mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); 295 | } 296 | 297 | @Override 298 | protected void onMethodExit(int opcode) { 299 | mv.visitFieldInsn(GETSTATIC, owner, "timer", "J"); 300 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", 301 | "currentTimeMillis", "()J"); 302 | mv.visitInsn(LADD); 303 | mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J"); 304 | } 305 | 306 | @Override 307 | public void visitMaxs(int maxStack, int maxLocals) { 308 | super.visitMaxs(maxStack + 4, maxLocals); 309 | } 310 | } 311 | ``` 312 | -------------------------------------------------------------------------------- /docs/notes/4.0元数据.md: -------------------------------------------------------------------------------- 1 | 本章解释如何用核心 API 生成和转换编译后的 Java 类元数据,比如注释。每一节都首先介绍一种元数据类型,然后给出用于生成和转换这些元数据的相应 ASM 接口、组件和工具,并给出一些说明性示例。 -------------------------------------------------------------------------------- /docs/notes/4.1泛型.md: -------------------------------------------------------------------------------- 1 | # 4.1 泛型 2 | 3 | 诸如 `List` 之类的泛型类,以及使用它们的类,包含了有关它们所声明或使用的泛型的信息。这一信息不是由字节代码指令在运行时使用,但可通过反射 API 访问。它还可以供编译器使用,以进行分离编译。 4 | 5 | ## 4.1.1 结构 6 | 7 | 出于后向兼容的原因,有关泛型的信息没有存储在类型或方法描述符中(它们的定义远早于Java 5 中对泛型的引入),而是保存在称为类型、方法和类*签名*的类似构造中。在涉及泛型时,除了描述符之外,这些签名也会存储在类、字段和方法**声明**中(泛型不会影响方法的字节代码: 编译器用它们执行静态类型检查,但会在必要时重新引入类型转换,就像这些方法未被使用一样进行编译)。 8 | 9 | 与类型和方法描述符不同,类型签名的语法非常复杂,这也是因为泛型的递归本质造成的(一个泛型可以将另一泛型作为参数——例如,考虑 `List>` )。其语法由以下规则给出(有关这些规则的完整描述,请参阅 **《Java 虚拟机规范》**): 10 | 11 | ```java 12 | TypeSignature: Z | C | B | S | I | F | J | D | FieldTypeSignature 13 | FieldTypeSignature: ClassTypeSignature | [ TypeSignature | TypeVar 14 | ClassTypeSignature: L Id ( / Id )* 15 | TypeArgs? ( . Id TypeArgs? )* ; 16 | TypeArgs: < TypeArg+ > 17 | TypeArg: * | ( + | - )? FieldTypeSignature 18 | TypeVar: T Id ; 19 | ``` 20 | 21 | 第一条规则表明,类型签名或者是一个基元类型描述符,或者是一个字段类型签名。第二条规则将一个字段类型签名定义为一个类类型签名、数组类型签名或类型变量。第三条规则定义类类型签名:它们是类类型描述符,在主类名之后或者内部类名之后的尖括号中可能带有类型参数 (以点为前缀)。其他规则定义了类型参数和类型变量。注意,一个类型参数可能是一个完整的字段类型签名,带有它自己的类型参数:因此,类型签名可能非常复杂(见图 4.1)。 22 | 23 | | Java 类型 | 相应的类型签名 | 24 | |:---|:---| 25 | | List | Ljava/util/List; | 26 | | List | Ljava/util/List<*>; | 27 | | List | Ljava/util/List<+Ljava/lang/Number;>; | 28 | | List | Ljava/util/List<-Ljava/lang/Integer;>; | 29 | | List[]> | Ljava/util/List<[Ljava/util/List;>; | 30 | | HashMap.HashIterator | Ljava/util/HashMap.HashIterator; | 31 | 32 | 方法签名扩展了方法描述符,就像类型签名扩展了类型描述符。**方法签名**描述了方法参数的类型签名及其返回类型的签名。与方法描述符不同的是,它还包含了该方法所抛出异常的签名, 前面带有^前缀,还可以在尖括号之间包含可选的形式类型参数: 33 | 34 | ```java 35 | MethodTypeSignature: 36 | TypeParams? ( TypeSignature* ) ( TypeSignature | V ) Exception* 37 | Exception: ^ClassTypeSignature | ^TypeVar 38 | TypeParams: < TypeParam+ > 39 | TypeParam: Id : FieldTypeSignature? ( : FieldTypeSignature )* 40 | ``` 41 | 42 | 比如以下泛型静态方法的方法签名,它以类型变量 T 为参数: 43 | 44 | ```static Class m (int n)``` 45 | 46 | 它是以下方法签名: 47 | 48 | ```(I)Ljava/lang/Class<+TT;>;``` 49 | 50 | 最后要说的是**类签名**,不要将它与类类型签名相混淆,它被定义为其超类的类型签名,后面跟有所实现接口的类型签名,以及可选的形式类型参数: 51 | 52 | ```ClassSignature: TypeParams? ClassTypeSignature ClassTypeSignature*``` 53 | 54 | 例 如 , 一 个 被 声 明 为 ```C extends List``` 的 类 的 类 签 名 就 是 ```Ljava/util/List;```。 55 | 56 | ## 4.1.2 接口与组件 57 | 58 | 和描述符的情况一样,也出于相同的效果原因(见 2.3.1 节),ASM API 公开签名的形式与它们在编译类中的存储形式相同(签名主要出现在 ClassVisitor 类的 visit、visitField 和 visitMethod 方法中,分别作为可选类、类型或方法签名参数 )。幸好它还在 org.objectweb.asm.signature 包中提供了一些基于 SignatureVisitor 抽象类的工具,用于生成和转换签名(见图 4.2)。 59 | 60 | >图 4.2 SignatureVisitor 类 61 | 62 | ```java 63 | public abstract class SignatureVisitor { 64 | public final static char EXTENDS = ’+’; 65 | public final static char SUPER = ’-’; 66 | public final static char INSTANCEOF = ’=’; 67 | 68 | public SignatureVisitor(int api); 69 | 70 | public void visitFormalTypeParameter(String name); 71 | 72 | public SignatureVisitor visitClassBound(); 73 | 74 | public SignatureVisitor visitInterfaceBound(); 75 | 76 | public SignatureVisitor visitSuperclass(); 77 | 78 | public SignatureVisitor visitInterface(); 79 | 80 | public SignatureVisitor visitParameterType(); 81 | 82 | public SignatureVisitor visitReturnType(); 83 | 84 | public SignatureVisitor visitExceptionType(); 85 | 86 | public void visitBaseType(char descriptor); 87 | 88 | public void visitTypeVariable(String name); 89 | 90 | public SignatureVisitor visitArrayType(); 91 | 92 | public void visitClassType(String name); 93 | 94 | public void visitInnerClassType(String name); 95 | 96 | public void visitTypeArgument(); 97 | 98 | public SignatureVisitor visitTypeArgument(char wildcard); 99 | 100 | public void visitEnd(); 101 | } 102 | ``` 103 | 104 | 这个抽象类用于访问类型签名、方法签名和类签名。用于类型签名的方法以粗体显示,必须按以下顺序调用,它反映了前面的语法规则(注意,其中两个返回了 SignatureVisitor:这是因为类型签名的递归定义导致的): 105 | 106 | ```java 107 | visitBaseType | visitArrayType | visitTypeVariable | ( visitClassType visitTypeArgument* 108 | ( visitInnerClassType visitTypeArgument* )* visitEnd ) ) 109 | ``` 110 | 111 | 用于访问方法签名的方法如下: 112 | 113 | ```java 114 | ( visitFormalTypeParameter visitClassBound? visitInterfaceBound* )* 115 | visitParameterType* visitReturnType visitExceptionType* 116 | ``` 117 | 118 | 最后,用于访问类签名的方法为: 119 | 120 | ```java 121 | ( visitFormalTypeParameter visitClassBound? visitInterfaceBound* )* 122 | visitSuperClass visitInterface* 123 | ``` 124 | 125 | 这些方法大多返回一个 SignatureVisitor:它是准备用来访问类型签名的。注意,不同于 ClassVisitor 返 回 的 MethodVisitors , SignatureVisitor 返 回 的 SignatureVisitors 不得为 null,而且必须顺序使用:事实上,在完全访问一个嵌套签名之前,不得访问父访问器的任何方法。 126 | 127 | 和类的情况一样,ASM API 基于这个 API 提供了两个组件:SignatureReader 组件分析一个签名,并针对一个给定的签名访问器调用适当的访问方法;SignatureWriter 组件基于它接收到的方法调用生成一个签名。 128 | 129 | 利用与类和方法相同的原理,这两个类可用于生成和转换签名。例如,假定我们希望对出现在某些签名中的类名进行重命名。这一效果可以用以下签名适配器完成,除 visitClassType 和 visitInnerClassType 方法之外,它将自己接收到的所有其他方法调用都不加修改地加以转发(这里假设 sv 方法总是返回 this,SignatureWriter 就属于这种情况): 130 | 131 | ```java 132 | public class RenameSignatureAdapter extends SignatureVisitor { 133 | private SignatureVisitor sv; 134 | private Map renaming; 135 | private String oldName; 136 | 137 | public RenameSignatureAdapter(SignatureVisitor sv, 138 | Map renaming) { 139 | super(ASM4); 140 | this.sv = sv; 141 | this.renaming = renaming; 142 | } 143 | 144 | public void visitFormalTypeParameter(String name) { 145 | sv.visitFormalTypeParameter(name); 146 | } 147 | 148 | public SignatureVisitor visitClassBound() { 149 | sv.visitClassBound(); 150 | return this; 151 | } 152 | 153 | public SignatureVisitor visitInterfaceBound() { 154 | sv.visitInterfaceBound(); 155 | return this; 156 | } 157 | ... 158 | 159 | public void visitClassType(String name) { 160 | oldName = name; 161 | String newName = renaming.get(oldName); 162 | sv.visitClassType(newName == null ? name : newName); 163 | } 164 | 165 | public void visitInnerClassType(String name) { 166 | oldName = oldName + "." + name; 167 | String newName = renaming.get(oldName); 168 | sv.visitInnerClassType(newName == null ? name : newName); 169 | } 170 | 171 | public void visitTypeArgument() { 172 | sv.visitTypeArgument(); 173 | } 174 | 175 | public SignatureVisitor visitTypeArgument(char wildcard) { 176 | sv.visitTypeArgument(wildcard); 177 | return this; 178 | } 179 | 180 | public void visitEnd() { 181 | sv.visitEnd(); 182 | } 183 | } 184 | ``` 185 | 186 | 因此,以下代码的结果为"LA.B;": 187 | 188 | ```java 189 | String s = "Ljava/util/HashMap.HashIterator;"; 190 | Map renaming = new HashMap(); 191 | renaming.put("java/util/HashMap", "A"); 192 | renaming.put("java/util/HashMap.HashIterator", "B"); 193 | SignatureWriter sw = new SignatureWriter(); 194 | SignatureVisitor sa = new RenameSignatureAdapter(sw, renaming); 195 | SignatureReader sr = new SignatureReader(s); 196 | sr.acceptType(sa); 197 | sw.toString(); 198 | ``` 199 | 200 | ## 4.1.3 工具 201 | 202 | 2.3 节给出的TraceClassVisitor 和ASMifier 类以内部形式打印类文件中包含的签名。利用它们,可以通过以下方式找出与一个给定泛型相对应的签名:编写一个具有某一泛型的 Java 类,编译它,并用这些命令行工具来找出对应的签名。 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /docs/notes/4.2注释.md: -------------------------------------------------------------------------------- 1 | # 4.2 注释 2 | 3 | 类、字段、方法和方法参数注释,比如 **@Deprecated** 或 **@Override**,只要它们的保留策略不是 **RetentionPolicy.SOURCE**,它们就会被存储在编译后的类中。这一信息不是在运行时供字节代码指令使用,但是,如果保留策略是 **RetentionPolicy.RUNTIME**,则可以通过反射 API 访问它。它还可以供编译器使用。 4 | 5 | ## 4.2.1 结构 6 | 7 | 源代码中的注释可以具有各种不同形式, 比如 **@Deprecated** 、 **@Retention(RetentionPolicy.CLASS)** 或 **@Task(desc="refactor", id=1)**。但在内部,所有注释的形式都是相同的,由一种注释类型和一组名称/值对规定,其中的取值仅限于如下几种: 8 | 9 | - 基元,String 或 Class 值 10 | - 枚举值 11 | - 注释值 12 | - 上述值的数组 13 | 14 | 注意,一个注释中可以包含其他注释,甚至可以包含注释数组。因此,注释可能非常复杂。 15 | 16 | ## 4.2.2 接口和组件 17 | 18 | 用于生成和转换注释的 **ASM API** 是基于 **AnnotationVisitor** 抽象类的(见图 4.3)。 19 | 20 | >图 4.3 AnnotationVisitor 类 21 | 22 | ```java 23 | public abstract class AnnotationVisitor { 24 | public AnnotationVisitor(int api); 25 | 26 | public AnnotationVisitor(int api, AnnotationVisitor av); 27 | 28 | public void visit(String name, Object value); 29 | 30 | public void visitEnum(String name, String desc, String value); 31 | 32 | public AnnotationVisitor visitAnnotation(String name, String desc); 33 | 34 | public AnnotationVisitor visitArray(String name); 35 | 36 | public void visitEnd(); 37 | } 38 | ``` 39 | 40 | 这个类的方法用于访问一个注释的名称/值对(注释类型在访问这一类型的方法中访问,即 visitAnnotation 方法)。第一个方法用于基元、String 和 Class 值(后者用 Type 对象表示),其他方法用于枚举、注释和数组值。可以按任意顺序调用它们,visitEnd 除外: 41 | 42 | ```java 43 | ( visit | visitEnum | visitAnnotation | visitArray )* visitEnd 44 | ``` 45 | 46 | 注意,两个方法返回 AnnotationVisitor:这是因为注释可以包含其他注释。另外,与 ClassVisitor 返回的 MethodVisitor 不同,这两个方法返回的 AnnotationVisitors 必须顺序使用:事实上,在完全访问一个嵌套注释之前,不能调用父访问器的任何方法。 47 | 48 | 还要注意,visitArray 方法返回一个 AnnotationVisitor,以访问数组的元素。但是, 由于数组的元素未被命名,因此,name 参数被 visitArray 返回的访问器的方法忽略,可以设定为 null。 49 | 50 | 1. 添加、删除和检测注释 51 | 52 | 与字段和方法的情景一样,可以通过在 visitAnnotation 方法中返回 null 来删除注释: 53 | 54 | ```java 55 | public class RemoveAnnotationAdapter extends ClassVisitor { 56 | private String annDesc; 57 | 58 | public RemoveAnnotationAdapter(ClassVisitor cv, String annDesc) { 59 | super(ASM4, cv); 60 | this.annDesc = annDesc; 61 | } 62 | 63 | @Override 64 | public AnnotationVisitor visitAnnotation(String desc, boolean vis) { 65 | if (desc.equals(annDesc)) { 66 | return null; 67 | } 68 | return cv.visitAnnotation(desc, vis); 69 | } 70 | } 71 | ``` 72 | 73 | 类注释的添加要更难一些,因为存在一些限制条件:必须调用 ClassVisitor 类的方法。事实上,所有可以跟在 visitAnnotation 之后的方法都必须重写,以检测什么时候已经访问了所有注释(因为 visitCode 方法的原因,方法注释的添加更容易一些): 74 | 75 | ```java 76 | public class AddAnnotationAdapter extends ClassVisitor { 77 | private String annotationDesc; 78 | private boolean isAnnotationPresent; 79 | 80 | public AddAnnotationAdapter(ClassVisitor cv, String annotationDesc) { 81 | super(ASM4, cv); 82 | this.annotationDesc = annotationDesc; 83 | } 84 | 85 | @Override 86 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 87 | cv.visit(v, access, name, signature, superName, interfaces); 88 | } 89 | 90 | @Override 91 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 92 | if (visible && desc.equals(annotationDesc)) { 93 | isAnnotationPresent = true; 94 | } 95 | return cv.visitAnnotation(desc, visible); 96 | } 97 | 98 | @Override 99 | public void visitInnerClass(String name, String outerName, String innerName, int access) { 100 | addAnnotation(); 101 | cv.visitInnerClass(name, outerName, innerName, access); 102 | 103 | } 104 | 105 | @Override 106 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 107 | addAnnotation(); 108 | return cv.visitField(access, name, desc, signature, value); 109 | } 110 | 111 | @Override 112 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 113 | addAnnotation(); 114 | return cv.visitMethod(access, name, desc, signature, exceptions); 115 | } 116 | 117 | @Override 118 | public void visitEnd() { 119 | addAnnotation(); 120 | cv.visitEnd(); 121 | } 122 | 123 | private void addAnnotation() { 124 | if (!isAnnotationPresent) { 125 | AnnotationVisitor av = cv.visitAnnotation(annotationDesc, true); 126 | if (av != null) { 127 | av.visitEnd(); 128 | } 129 | isAnnotationPresent = true; 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | 注意,如果类版本低于 1.5,这个适配器将其更新至该版本。这是必要地,因为对于版本低于 1.5 的类,JVM 会忽略其中的注释。 136 | 137 | 注释在类和方法适配器中的最后一种应用情景,也可能是最常见的应用情景,就是以注释实现转换的参数化。例如,你可能仅对于那些具有@Persistent 注释的字段来转换字段的访问, 仅对于那些拥有@Log 注释的方法添加记录代码,如此等等。所有这些应用情景都可以很轻松地实现,因为注释是必须首先访问的:必须在字段和方法之前访问类注释,必须在代码之前访问方法和参数注释。因此,只需在检测到所需注释时设定一个标志,然后在后面的转换中使用,就像上面的例子用 isAnnotationPresent 标志所做的事情。 138 | 139 | ## 4.2.3 工具 140 | 141 | 2.3 节介绍的 TraceClassVisitor, CheckClassAdapter 和 ASMifier 类也支持注释 ( 就 像 对 于 方 法 一 样 , 还 可 能 使 用 TraceAnnotationVisitor 或 CheckAnnotationAdapter,在各个注释的级别工作,而不是在类级别工作)。它们可用于查看如何生成某个特定注释。例如,使用以下代码: 142 | 143 | ```java 144 | java -classpath asm.jar:asm-util.jar \1 145 | org.objectweb.asm.util.ASMifier \ 146 | java.lang.Deprecated 147 | ``` 148 | 149 | 将输出如下代码(经过微小的重构): 150 | 151 | ```java 152 | package asm.java.lang; 153 | import org.objectweb.asm.*; 154 | 155 | public class DeprecatedDump implements Opcodes { 156 | 157 | public static byte[] dump() throws Exception { 158 | ClassWriter cw = new ClassWriter(0); 159 | AnnotationVisitor av; 160 | cw.visit(V1_5, ACC_PUBLIC + ACC_ANNOTATION + ACC_ABSTRACT 161 | + ACC_INTERFACE, "java/lang/Deprecated", null, "java/lang/Object", 162 | new String[]{"java/lang/annotation/Annotation"}); 163 | { 164 | av = cw.visitAnnotation("Ljava/lang/annotation/Documented;", true); 165 | av.visitEnd(); 166 | } 167 | { 168 | av = cw.visitAnnotation("Ljava/lang/annotation/Retention;", true); 169 | av.visitEnum("value", "Ljava/lang/annotation/RetentionPolicy;", 170 | "RUNTIME"); 171 | av.visitEnd(); 172 | } 173 | cw.visitEnd(); 174 | return cw.toByteArray(); 175 | } 176 | } 177 | ``` 178 | 179 | 此代码说明如何用 **ACC_ANNOTATION** 标志创建一个注释类,并说明如何创建两个类注释, 一个没有值,一个具有枚举值。方法注释和参数注释可以采用 **MethodVisitor** 类中定义的 **visitAnnotation** 和 **visitParameterAnnotation** 方法以类似方式创建。 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /docs/notes/4.3调试.md: -------------------------------------------------------------------------------- 1 | # 4.3 调试 2 | 3 | 以 `javac -g` 编译的类中包含了其源文件的名字、源代码行编号与字节代码指令之间的映射、源代码中局部变量名与字节代码中局部变量槽之间的映射。当这一可选信息可用时, 会在调试器中和异常栈轨迹中使用它们。 4 | 5 | ## 4.3.1 结构 6 | 7 | 一个类的源文件名存储在一个专门的类文件结构部分中(见图 2.1)。 8 | 9 | 源代码行编号与字节代码指令之间的映射存储为一个由(line number, label)对组成的列表中,放在方法的已编译代码部分中。例如,如果 l1、l2 和 l3 是按此顺序出现的三个标记,则下面各对: 10 | 11 | ```java 12 | (n1, l1) 13 | (n2, l2) 14 | (n3, l3) 15 | ``` 16 | 17 | 意味着 l1 和 l2 之间的指令来自行 n1,l2 和 l3 之间的指令来自 n2,l3 之后的指令来自行 n3。注意,一个给定行号可以出现在几个对中。这是因为,对于出现在一个源代码行中的表达式,其在字节代码中的相应指令可能不是连续的。例如,for (init; cond; incr) statement;通常是按以下顺序编译的: 18 | 19 | ```java 20 | init statement incr cond 21 | ``` 22 | 23 | 源代码中局部变量名与字节代码中局部变量槽之间的映射,以(name, type descriptor, type signature, start, end, index)等多元组列表的形式存储在该方法的已编译代码节中。这样一个多元组的含义是:在两个标记 start 和 end 之间,槽 index 中的局部变量对应于源代码中的局部变量,其名字和类型由多元组的前三个元素组出。注意,编译器可以使用相同的局部变量槽来存储具有不同作用范围的不同源局部变量。反之,同一个源代码局部变量可能被编译为一个具有非连续作用范围的局部变量槽。例如,有可能存在一种类似如下的情景: 24 | 25 | ```java 26 | l1: 27 | ... // 这里的槽 1 包含局部变量i 28 | l2: 29 | ... // 这里的槽 1 包含局部变量j 30 | l3: 31 | ... // 这里的槽 1 再次包含局部变量i 32 | end: 33 | ``` 34 | 35 | 相应的多元组为: 36 | 37 | ```java 38 | ("i", "I", null, l1, l2, 1) 39 | ("j", "I", null, l2, l3, 1) 40 | ("i", "I", null, l3, end, 1) 41 | ``` 42 | 43 | ## 4.3.2 接口和组件 44 | 45 | 调试信息用 ClassVisitor 和 MethodVisitor 类的三个方法访问: 46 | 47 | - 源文件名用 ClassVisitor 类的 visitSource 方法访问; 48 | - 源代码行号与字节代码指令之间的映射用 MethodVisitor 类的 visitLineNumber 方法访问,每次访问一对; 49 | - 源代码中局部变量名与字节代码中局部变量槽之间的映射用 MethodVisitor 类的 visitLocalVariable 方法访问,每次访问一个多元组。 50 | 51 | visitLineNumber 方法必须在已经访问了作为参数传送的标记之后进行调用。在实践中, 就是在访问这一标记后立即调用它,从而可以非常容易地知道一个方法访问器中当前指令的源代码行: 52 | 53 | ```java 54 | public class MyAdapter extends MethodVisitor { 55 | int currentLine; 56 | 57 | public MyAdapter(MethodVisitor mv) { 58 | super(ASM4, mv); 59 | } 60 | 61 | @Override 62 | public void visitLineNumber(int line, Label start) { 63 | mv.visitLineNumber(line, start); 64 | currentLine = line; 65 | } 66 | ... 67 | } 68 | ``` 69 | 70 | 类似地,visitLocalVariable 方法方法必须在已经访问了作为参数传送的标记之后调用。下面给出一些方法调用示例,它们对应于上一节给出的名称值对和多元组: 71 | 72 | ```java 73 | visitLineNumber(n1, l1); 74 | visitLineNumber(n2, l2); 75 | visitLineNumber(n3, l3); 76 | visitLocalVariable("i", "I", null, l1, l2, 1); 77 | visitLocalVariable("j", "I", null, l2, l3, 1); 78 | visitLocalVariable("i", "I", null, l3, end, 1); 79 | ``` 80 | 81 | 1. 忽略调试信息 82 | 83 | 为了访问行号和局部变量名,ClassReader 类可能需要引入“人为”Label 对象,也就是说,跳转指令并不需要它们,它们只是为了表示调试信息。这可能会在诸如 3.2.5 节介绍的情景中导致错误判断,在该情景中,指令序列中部的一个 Label 被认为是一个跳转目标,因此禁止这一序列被删除。 84 | 85 | 为避免这种误判,可以在 ClassReader.accept 方法中使用 SKIP_DEBUG 选项。有了这一选项,类读取器不会访问调试信息,不会为它创建人为标记。当然,调试信息会从类中删除, 因此,只有在不会为应用程序造成问题时才能使用这一选项。 86 | 87 | ```java 88 | 注意:ClassReader 类提供了其他一些选项,比如:SKIP_CODE,用于跳过对已编译代码的访问(如果只需要类的结构,那这个选项是很有用的);SKIP_FRAMES,用于跳过栈映射帧;EXPAND_FRAMES,用于解压缩这些帧。 89 | ``` 90 | 91 | ## 4.3.3 工具 92 | 93 | 和泛型与注释的情景一样,可以使用 **TraceClassVisitor**、**CheckClassAdapter** 和 **ASMifier** 类来了解如何使用调试信息。 -------------------------------------------------------------------------------- /docs/notes/5.0后向兼容.md: -------------------------------------------------------------------------------- 1 | # 5.0 后向兼容 -------------------------------------------------------------------------------- /docs/notes/5.1引言.md: -------------------------------------------------------------------------------- 1 | # 5.1 引言 2 | 3 | 过去已经在类文件格式中引入了新的元素,未来还将继续添加新元素(例如,用于模块化、 Java 类型的注释,等等)。到 ASM 3.x,这样的每一次变化都会导致 ASM API 中的后向不兼容变化,这不是件好事情。为解决这些问题,ASM 4.0 中已经引入了一种新机制。它的目的是确保未来所有 ASM 版本都将与之前直到 ASM 4.0 的任意版本保持后向兼容,即使向类文件格式中引入了新的功能时也能保持这种兼容性。这意味着,从 4.0 开始,为一个 ASM 版本编写的类生成器、类分析器或类适配器,将可以在任何未来 ASM 版本中使用。但是,仅靠 ASM 自身是不能确保这一性质的。它需要用户在编写代码时遵循一些简单的准则。本章的目的就是介绍这些准则,并大致介绍一下 ASM 核心 API 中用于确保后向兼容性的内部机制。 4 | 5 | ```java 6 | 注意: ASM 4.0 中引入的后向兼容机制要求将 ClassVisitor 、 FieldVisitor 、 MethodVisitor 等由接口变为抽象类,具有一个以 ASM 版本为参数的构造器。如果你的代码是为 ASM 3.x 实现的,可以将其升级至 ASM 4.0:将代码分析器和适配器中的 implements 用 extends 替换, 并在它们的构造器中指定一个 ASM 版本。此外,ClassAdapter 和 MethodAdapter 还被合并到 ClassVisitor 和 MethodVisitor 中。要转换代码,只需用 ClassVisitor 代替 ClassAdapter,用 MethodVisitor 代替 MethodAdapter。另外,如果定义了自定义的 FieldAdapter 或 AnnotationAdapter 类 , 现 在 可 以 用 FieldVisitor 和 AnnotationVisitor 代替它们。 7 | ``` 8 | 9 | ## 5.1.1 后向兼容约定 10 | 11 | 在给出用以确保后向兼容性的规则之前,首先给出“后向兼容”的更准确定义。 12 | 13 | 首先,研究一下新的类文件特征如何影响代码生成器、分析器和适配器是非常重要的。也就是说,在不受任何实现和二进制兼容问题影响时,在引入这些新特征之前设计的类生成器、分析器或适配器在进行这些修改之后还是否有效?换言之,如果有一个在引入这些新功能之前设计的转换链,假定这些新功功直接被忽略,原封不动地通过转换链,那这个转换链还是否依然有效? 事实上,类生成器、分析器和适配器受到的影响是不同的: 14 | 15 | - 类生成器不受影响:它们生成具有某一固定类版本的代码,这些生成的类在未来的 JVM 版本中依然有效,因为 JVM 确定了后向二进制兼容。 16 | - 类分析器可能受到影响,也可能不受影响。例如,有一段用于分析字节代码指令的代码, 它是为 Java 4 编写的,它也许能够正常处理 Java 5 类,尽管 Java 5 中引入了注释。但同一段代码也许不再能处理 Java 7 类,因为它不能忽略新的动态调用指令。 17 | - 类适配器可能受到影响,也可能不受影响。死代码清除工具不会因为引入注释而受到影响,甚至不会受到新的动态调用指令的影响。但另一方面,这两种新特性可能都会影响到为类进行重命名的工具。 18 | 19 | 这表明,新的类文件特性可能会对已有的类分析器或适配器产生不可预测的影响。如果新的特性直接被忽略,原封不动地通过一个分析链或转换链,这个链在某些情况下可以运行,不产生错误,并给出有效结果,而在某些情况下,也可以运行,不产生错误,但却给出无效结果,而在另外一些情况下,可能会在执行期间失败。第二种情景的问题尤其严重,因为它会在用户不知晓的情况下破坏分析链或转换链的语义,从而导致难以找出 Bug。为解决这一问题,我们认为最好不要忽略新特性,而是只要在分析链或转换链中遇到未知特性,就产生一条错误。这种错误发出信号:这个链也许能够处理新的类格式,也许不能,链的编写者必须分析具体情景,并在必要时进行更新。 20 | 21 | 所有上述内容引出了后向兼容性约定的如下定义: 22 | 23 | - SM 版本 X 是为版本号低于小等于 x 的 Java 类编写的。它不能生成版本号 y>x 的类, 如果在 ClassReader.accept 中,以一个版本号大于 x 的类作为输入,它必须失败。 24 | - 于为 ASM X 编写且遵循了以下所述规则的代码,当输入类的版本不超过 x,对于 ASM 未来任意大于 X 的版本 Y,该代码都能不加修改地正常工作。 25 | - 于为 ASM X 编写且遵循了以下所述规则的代码,当输入类的声明版本为 y,但仅使用了在不晚于版本 x 中定义的功能,则在使用 ASM Y 或任意未来版本时,该代码能够不加修改地正常工作。 26 | - 于为 ASM X 编写且遵循了以下所述规则的代码,当输入类使用了在版本号为 y>x 的类中定义的功能时,对于 ASM X 或任意其他未来版本,该代码都必须失败。 27 | 28 | *注意,最后三点与类生成器无关,因为它没有类输入。* 29 | 30 | ## 5.1.2 一个例子 31 | 32 | 为说明这些用户规则及用于保证后向兼容性的内部 ASM 机制,本章假定将向 Java 8 类中添加两个新的假设属性,一个用于存储类的作者,另一个用于存储它的许可。还假设这些新的属性在 ASM 5.0 中通过 ClassVisitor 的两个新方法公开,一个是: 33 | 34 | ```java 35 | void visitLicense(String license); 36 | ``` 37 | 38 | 用于访问许可,还有一个是 visitSource 的新版本,用于在访问源文件名和调试信息的同时访问作者①: 39 | 40 | ```java 41 | @Deprecated void visitSource(String source, String debug); 42 | ``` 43 | 44 | *① 事实上,可能仅添加一个visitLicense(String author, String license)方法,因为修改一个方法签名要比添加一个方法更复杂,如下所示。这里的做法只是出于说明目的。* 45 | 46 | 作者和许可属性是可选的,即对 visitLicense 的调用并非强制的,在一个 visitSource 调用中,author 可能是 null。 47 | 48 | -------------------------------------------------------------------------------- /docs/notes/5.2规则.md: -------------------------------------------------------------------------------- 1 | # 5.2 规则 2 | 3 | 本节给出一些规则,在使用 ASM API 时,要想确保你的代码在所有未来 ASM 版本中都有效(其意义见上述约定),就必须遵循这些规则。 4 | 5 | 首先,如果编写一个类生成器,那不需要遵循任何规则。例如,如果正在为 ASM 4.0 编写一个类生成器,它可能包含一个类似于 visitSource(mySource, myDebug)的调用,当然不包含对 visitLicense 的调用。如果不加修改地用 ASM 5.0 运行它,它将会调用过时的 visitSource 方法 ,但 ASM 5.0 ClassWriter 将会在 内 部将它 重 定向到visitSource(null, mySource, myDebug),生成所期望的结果(但其效率要稍低于直接将代码升级为调用这个新方法)。同理,缺少对 visitLicense 的调用也不会造成问题(所生成的类版本也没有变化,人们并不指望这个版本的类中会有一个许可属性)。 6 | 7 | 另一方面,如果编写一个类分析器或类适配器,也就是说,如果重写 ClassVisitor 类(或者任何其他类似的类,比如 FieldVisitor 或 MethodVisitor),就必须遵循一些规则,如下所述。 8 | 9 | ## 5.2.1 基本规则 10 | 11 | 这里考虑一个类的简单情况:直接扩展 ClassVisitor(讨论和规则对于其他访问器类都是相同的;间接子类的情景在下一节讨论)。在这种情况下,只有一条规则: 12 | 13 | **规则 1**:要为 ASM X 编写一个 ClassVisitor 子类,就以这个版本号为参数,调用 ClassVisitor 构造器,在这个版本的 ClassVisitor 类中,**绝对不要重写或调用弃用的方法**(或者将在之后版本引入的方法)。 14 | 15 | 就这么多。在我们的示例情景中(见 5.1.2 节),为 ASM 4.0 编写的类适配器必须看起来类似于如下所示: 16 | 17 | ```java 18 | class MyClassAdapter extends ClassVisitor { 19 | public MyClassAdapter(ClassVisitor cv) { 20 | super(ASM4, cv); 21 | } 22 | ... 23 | 24 | public void visitSource(String source, String debug) { // optional 25 | ... 26 | super.visitSource(source, debug); // optional 27 | } 28 | } 29 | ``` 30 | 31 | 一旦针对 ASM5.0 升级之后,必须删除 visitSource(String, String),这个类看起来必须类似于如下所示: 32 | 33 | ```java 34 | class MyClassAdapter extends ClassVisitor { 35 | 36 | public MyClassAdapter(ClassVisitor cv) { 37 | super(ASM5, cv); 38 | } 39 | ... 40 | 41 | public void visitSource(String author, 42 | String source, String debug) { // optional 43 | ... 44 | super.visitSource(author, source, debug); // optional 45 | } 46 | 47 | public void visitLicense(String license) { // optional 48 | ... 49 | super.visitLicense(license); // optional 50 | } 51 | } 52 | ``` 53 | 54 | 它是如何工作的呢?在 ASM4.0中,ClassVisitor 的内部实现如下: 55 | 56 | ```java 57 | public abstract class ClassVisitor { 58 | int api; 59 | ClassVisitor cv; 60 | 61 | public ClassVisitor(int api, ClassVisitor cv) { 62 | this.api = api; 63 | this.cv = cv; 64 | } 65 | ... 66 | 67 | public void visitSource(String source, String debug) { 68 | if (cv != null) cv.visitSource(source, debug); 69 | } 70 | } 71 | ``` 72 | 73 | 在 ASM 5.0 中,这一代码变为: 74 | 75 | ```java 76 | public abstract class ClassVisitor { 77 | ... 78 | 79 | public void visitSource(String source, String debug) { 80 | if (api < ASM5) { 81 | if (cv != null) cv.visitSource(source, debug); 82 | } else { 83 | visitSource(null, source, debug); 84 | } 85 | } 86 | 87 | public void visitSource(Sring author, String source, String debug) { 88 | if (api < ASM5) { 89 | if (author == null) { 90 | visitSource(source, debug); 91 | } else { 92 | throw new RuntimeException(); 93 | } 94 | } else { 95 | if (cv != null) cv.visitSource(author, source, debug); 96 | } 97 | } 98 | 99 | public void visitLicense(String license) { 100 | if (api < ASM5) throw new RuntimeException(); 101 | if (cv != null) cv.visitSource(source, debug); 102 | } 103 | } 104 | ``` 105 | 106 | 如果 MyClassAdapter 4.0 扩展了 ClassVisitor 4.0,那一切都将如预期中一样正常工作。如果升级到 ASM 5.0,但没有修改代码,MyClassAdapter 4.0 现在将扩展 ClassVisitor 5.0。但api 字段仍将是ASM4 图 6.1 ClassNode 类(仅给出了字段) 8 | 9 | ```java 10 | public class ClassNode ...{ 11 | public int version; 12 | public int access; 13 | public String name; 14 | public String signature; 15 | public String superName; 16 | public List interfaces; 17 | public String sourceFile; 18 | public String sourceDebug; 19 | public String outerClass; 20 | public String outerMethod; 21 | public String outerMethodDesc; 22 | public List visibleAnnotations; 23 | public List invisibleAnnotations; 24 | public List attrs; 25 | public List innerClasses; 26 | public List fields; 27 | } 28 | ``` 29 | 30 | 可以看出,这个类的公共字段对应于图 2.1 中给出的类文件结构部分。这些字段的内容与核心 API 相同。例如,name 是一个内部名字,signature 是一个类签名(见 2.1.2 节和 4.1 节)。一些字段包含其他 XxxNode 类:这些类将在随后各章详细介绍,它们拥有一种类似的结构,即拥有一些字段,对应于类文件结构的子部分。例如,FieldNode 类看起来是这样的: 31 | 32 | ```java 33 | public class FieldNode ...{ 34 | 35 | public int access; 36 | public String name; 37 | public String desc; 38 | public String signature; 39 | public Object value; 40 | public FieldNode(int access,String name,String desc,String signature,Object value){ 41 | ... 42 | } 43 | ... 44 | } 45 | ``` 46 | 47 | MethodNode 类是类似的: 48 | 49 | ```java 50 | public class MethodNode ... { 51 | public int access; 52 | public String name; 53 | public String desc; 54 | public String signature; 55 | public List exceptions; 56 | ... 57 | public MethodNode(int access, String name, String desc, String signature, String[] exceptions){ 58 | ... 59 | } 60 | } 61 | ``` 62 | 63 | ## 6.1.2 生成类 64 | 65 | 用树 API 生成类的过程就是:创建一个 ClassNode 对象,并初始化它的字段。例如,2.2.3 节的 Comarable 接口可用如下代码生成(其代码数量大体与 2.2.3 节相同): 66 | 67 | ```java 68 | ClassNode cn = new ClassNode(); 69 | cn.version = V1_5; 70 | cn.access = ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE; 71 | cn.name = "pkg/Comparable"; cn.superName = "java/lang/Object"; cn.interfaces.add("pkg/Mesurable"); 72 | cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, new Integer(-1))); 73 | cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, new Integer(0))); 74 | cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I", null, new Integer(1))); 75 | cn.methods.add(new MethodNode(ACC_PUBLIC + ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null)); 76 | ``` 77 | 78 | 使用树 API 生成类时,需要多花费大约 30%的时间(见附录 A.1),占用的内存也多于使用核心 API。但可以按任意顺序生成类元素,这在一些情况下可能非常方便。 79 | 80 | ## 6.1.3 添加和删除类成员 81 | 82 | 添加和删除类就是在 ClassNode 对象的 fields 或 methods 列表中添加或删除元素。例如,如果像下面这样定义了 ClassTransformer 类,以便能够轻松地编写类转换器: 83 | 84 | ```java 85 | public class ClassTransformer { 86 | protected ClassTransformer ct; 87 | 88 | public ClassTransformer(ClassTransformer ct) { 89 | this.ct = ct; 90 | } 91 | 92 | public void transform(ClassNode cn) { 93 | if (ct != null) { 94 | ct.transform(cn); 95 | } 96 | } 97 | } 98 | ``` 99 | 100 | 则 2.2.5 节中的 RemoveMethodAdapter 可实现如下: 101 | 102 | ```java 103 | public class RemoveMethodTransformer extends ClassTransformer { 104 | private String methodName; 105 | private String methodDesc; 106 | 107 | public RemoveMethodTransformer(ClassTransformer ct, String methodName, String methodDesc) { 108 | super(ct); 109 | this.methodName = methodName; 110 | this.methodDesc = methodDesc; 111 | } 112 | 113 | @Override 114 | public void transform(ClassNode cn) { 115 | Iterator i = cn.methods.iterator(); 116 | while (i.hasNext()) { 117 | MethodNode mn = i.next(); 118 | if (methodName.equals(mn.name) && methodDesc.equals(mn.desc)) { 119 | i.remove(); 120 | } 121 | } 122 | super.transform(cn); 123 | } 124 | } 125 | ``` 126 | 127 | 可以看出,它与核心 API 的主要区别是需要迭代所有方法,而在使用核心 API 时是不需要这样做的(这一工作会在 ClassReader 中为你完成)。事实上,这一区别对于几乎所有基于树的转换都是有效的。例如,在用树 API 实现 2.2.6 节的 AddFieldAdapter 时,它还需要一个迭代器: 128 | 129 | ```java 130 | public class AddFieldTransformer extends ClassTransformer { 131 | private int fieldAccess; 132 | private String fieldName; 133 | private String fieldDesc; 134 | 135 | public AddFieldTransformer(ClassTransformer ct, int fieldAccess, String fieldName, String fieldDesc) { 136 | super(ct); 137 | this.fieldAccess = fieldAccess; 138 | this.fieldName = fieldName; 139 | this.fieldDesc = fieldDesc; 140 | } 141 | 142 | @Override 143 | public void transform(ClassNode cn) { 144 | boolean isPresent = false; 145 | for (FieldNode fn : cn.fields) { 146 | if (fieldName.equals(fn.name)) { 147 | isPresent = true; 148 | break; 149 | } 150 | } 151 | if (!isPresent) { 152 | cn.fields.add(new FieldNode(fieldAccess, fieldName, fieldDesc, 153 | 154 | null, null)); 155 | } 156 | super.transform(cn); 157 | } 158 | } 159 | ``` 160 | 161 | 和生成类的情景一样,使用树 API 转换类时,所花费的时间和占用的内存也要多于使用核心 API 的时候。但使用树 API 有可能使一些转换的实现更为容易。比如有一个转换,要向一个类中添加注释,包含其内容的数字签名,就属于上述情景。在使用核心 API 时,只有在访问了整个类之后才能计算数字签名,但这时再添加包含其内容的注释就太晚了,因为对注释的访问必须位于类成员之前。而在使用树 API 时,这个问题就消失了,因为这时不存在此种限制。 162 | 163 | 事实上,有可能用核心 API 实现 AddDigitialSignature 示例,但随后,必须分两遍来转换这个类。第一遍,首先用一个 ClassReader(没有 ClassWriter)来访问这个类,以根据类的内容来计算数字签名。在第二遍,重复利用同一个 ClassReader 对类进行第一次访问, 这一次是向一个 ClassWriter 链接一个 AddAnnotationAdapter。通过推广这一论述过程, 我们可以看出,事实上,任何转换都可以仅用核心 API 来实现,只需在必要时分几遍完成。但这样就提高了转换代码的复杂性,要求在各遍之间存储状态(这种状态可能非常复杂,需要一个完整的树形表示!),而且对一个类进行多次分析是有成本的,必需将这一成本与构造相应 ClassNode 的成本进行比较。 164 | 165 | 结论是:**树 API 通常用于那些不能由核心 API 一次实现的转换**。但当然也存在例外。例如一个混淆器不能由核心 API 一遍实现,因为必须首先在原名称和混淆后的名字之间建立了完整的映射之后,才可能转换类,而这个映射的建立需要对所有类进行分析。但树 API 也不是一个好的解决方案,因为它需要将所有待混淆类的对象表示保存在内存中。在这种情况下,最好是分两遍使用核心 API:一遍用于计算原名与混淆后名称之间的映射(一个简单的散列表,它需要的内存要远少于所有类的完整对象表示),另一遍用于根据这一映射来转换类。 -------------------------------------------------------------------------------- /docs/notes/6.2组件合成.md: -------------------------------------------------------------------------------- 1 | # 6.2 组件合成 2 | 3 | 到现在为止,我们只是看到了如何创建和转换 ClassNode 对象,但还没有看到如何由一个类的字节数组表示来构造一个 ClassNode,或者反过来,由 ClassNode 构造这个字节数组。事实上,这一功能可以通过合成核心 API 和树 API 组件来完成,本节就来解释这一内容。 4 | 5 | ## 6.2.1 介绍 6 | 7 | 除了图 6.1 所示的字段之外,ClassNode 类扩展了 ClassVisitor 类,还提供了一个 accept 方法,它以一个 ClassVisitor 为参数。Accept 方法基于 ClassNode 字段值生成事件,而 ClassVisitor 方法执行逆操作,即根据接到的事件设定 ClassNode 字段: 8 | 9 | ```java 10 | public class ClassNode extends ClassVisitor { 11 | ... 12 | 13 | public void visit(int version, int access, String name, 14 | String signature, String superName, String[] interfaces[]) { 15 | this.version = version; 16 | 17 | this.access = access; 18 | this.name = name; 19 | this.signature = signature; 20 | ... 21 | } 22 | ... 23 | 24 | public void accept(ClassVisitor cv) { 25 | cv.visit(version, access, name, signature, ...); 26 | ... 27 | } 28 | } 29 | ``` 30 | 31 | 要由字节数组构建 ClassNode,可以将它与 ClassReader 合在一起,使 ClassReader 生成的事件可供 ClassNode 组件使用,从而初始化其字段(由上述代码可以看出): 32 | 33 | ```java 34 | ClassNode cn = new ClassNode(); 35 | ClassReader cr = new ClassReader(...); 36 | cr.accept(cn, 0); 37 | ``` 38 | 39 | 反过来,可以将 ClassNode 转换为其字节数组表示,只需将它与 ClassWriter 合在一起即可,从而使 ClassNode 的 accept 方法生成的事件可供 ClassWriter 使用: 40 | 41 | ```java 42 | ClassWriter cw = new ClassWriter(0); 43 | cn.accept(cw); 44 | byte[] b = cw.toByteArray(); 45 | ``` 46 | 47 | ## 6.2.2 模式 48 | 49 | 要用树 API 转换类,可以将这些元素放在一起: 50 | 51 | ```java 52 | ClassNode cn = new ClassNode(ASM4); 53 | ClassReader cr = new ClassReader(...); 54 | cr.accept(cn, 0); 55 | ... // 可以在这里根据需要转换 cn 56 | ClassWriter cw = new ClassWriter(0); 57 | cn.accept(cw); 58 | byte[] b = cw.toByteArray(); 59 | ``` 60 | 61 | 还可能与核心 API 一起使用基于树的类转换器,比如类适配器。有两种常见模式可用于此种情景。第一种模式使用继承: 62 | 63 | ```java 64 | public class MyClassAdapter extends ClassNode { 65 | public MyClassAdapter(ClassVisitor cv) { 66 | super(ASM4); 67 | this.cv = cv; 68 | } 69 | 70 | @Override 71 | public void visitEnd() { 72 | // put your transformation code here 73 | accept(cv); 74 | } 75 | } 76 | ``` 77 | 78 | 当这个类适配器用在一个经典的转换链时: 79 | 80 | ```java 81 | ClassWriter cw = new ClassWriter(0); 82 | ClassVisitor ca = new MyClassAdapter(cw); 83 | ClassReader cr = new ClassReader(...); 84 | cr.accept(ca, 0); 85 | byte[] b = cw.toByteArray(); 86 | ``` 87 | 88 | cr 生成的事件供 ClassNode ca 使用,从而初始化这个对象的字段。最后,在使用 visitEnd 事件时,ca 执行转换,并通过调用其 accept 方法,生成与所转换类对应的新事件,然后由 cw 使用。如果假定 ca 改变了类版本,则相应原程序图如图 6.2 所示。 89 | 90 | ![图 6.2 MyClassAdapter 的程序图](http://asm.itstack.org/assets/img/2020/6.2-1.png) 91 | 92 | 与图 2.7 中 ChangeVersionAdapter 的程序图进行对比,可以看出,ca 和 cw 之间的事件发生在 cr 和 ca 之间的事件之后,而不是像正常类适配器一样同时进行。事实上,对于所有基于树的转换都是如此,同时还解释了为什么它们受到的限制要少于基于事件的转换。 93 | 94 | 第二种模式可用于以类似程序图获得相同结果,它使用的是委托而非继承: 95 | 96 | ```java 97 | public class MyClassAdapter extends ClassVisitor { 98 | ClassVisitor next; 99 | 100 | public MyClassAdapter(ClassVisitor cv) { 101 | super(ASM4, new ClassNode()); 102 | next = cv; 103 | } 104 | 105 | @Override 106 | public void visitEnd() { 107 | ClassNode cn = (ClassNode) cv; 108 | // 将转换代码放在这里 109 | cn.accept(next); 110 | } 111 | } 112 | ``` 113 | 114 | 这一模式使用两个对象而不是一个,但其工作方式完全与第一种模式相同:接收到的事件用于构造一个 ClassNode,它被转换,并在接收到最后一个事件后,变回一个基于事件的表示。 115 | 116 | 这两种模式都允许用基于事件的适配器来编写基于树的类适配器。它们也可用于将基于树的适配器组合在一起,但如果只需要组合基于树的适配器,那这并非最佳解决方案:在这种情况下, 使用诸如 ClassTransformer 的类将会避免在两种表示之间进行不必要的转换。 -------------------------------------------------------------------------------- /docs/notes/7.0方法.md: -------------------------------------------------------------------------------- 1 | 本章解释如何用 ASM 树 API 生成和转换方法。首先介绍树 API 本身,给出一些说明性示例, 然后说明如何用核心 API 编写它。用于泛型和注释的树 API 在下一章介绍。 -------------------------------------------------------------------------------- /docs/notes/7.2组件合成.md: -------------------------------------------------------------------------------- 1 | # 7.2 组件合成 2 | 3 | 前为止,我们仅看到了如何创建和转换 MethodNode 对象,却还没有看到与类的字节数组表示进行链接。和类的情景一样,这一链接过程也是通过合成核心 API 和树 API 组件完成的,本节就来进行解释。 4 | 5 | ## 7.2.1 介绍 6 | 7 | 除了图 7.1 显示的字段之外,MethodNode 类扩展了 MethodVisitor 类,还提供了两个 accept 方法,它以一个 MethodVisitor 或一个 ClassVisitor 为参数。accept 方法基于 MethodNode 字段值生成事件,而 MethodVisitor 方法执行逆操作,即根据接收到的事件设定 MethodNode 字段。 8 | 9 | ## 7.2.2 模式 10 | 11 | 和类的情景一样,有可能与核心 API 使用一个基于树的方法转换器,比如一个方法适配器。用于类的两种模式实际上对于方法也是有效的,其工作方式完全相同。基于继承的模式如下: 12 | 13 | ```java 14 | public class MyMethodAdapter extends MethodNode { 15 | public MyMethodAdapter(int access, String name, String desc, String signature, String[] exceptions, MethodVisitor mv) { 16 | super(ASM4, access, name, desc, signature, exceptions); 17 | this.mv = mv; 18 | } 19 | 20 | @Override 21 | public void visitEnd() { 22 | // 将你的转换代码放在这儿 23 | accept(mv); 24 | } 25 | } 26 | ``` 27 | 28 | 而基于委托的模式为: 29 | 30 | ```java 31 | public class MyMethodAdapter extends MethodVisitor { 32 | MethodVisitor next; 33 | 34 | public MyMethodAdapter(int access, String name, String desc, String signature, String[] exceptions, MethodVisitor mv) { 35 | super(ASM4, 36 | new MethodNode(access, name, desc, signature, exceptions)); 37 | next = mv; 38 | } 39 | 40 | @Override 41 | public void visitEnd() { 42 | MethodNode mn = (MethodNode) mv; 43 | //将你的转换代码放在这儿 44 | mn.accept(next); 45 | } 46 | } 47 | ``` 48 | 49 | 第一种模式的一种变体是直接在 ClassAdapter 的 visitMethod 中将它与一个匿名内部类一起使用: 50 | 51 | ```java 52 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 53 | return new MethodNode(ASM4, access, name, desc, signature, exceptions) { 54 | @Override 55 | public void visitEnd() { 56 | //将你的转换代码放在这儿 57 | accept(cv); 58 | } 59 | }; 60 | } 61 | ``` 62 | 63 | 这些模式表明,可以将树 API 仅用于方法,将核心 API 用于类。**在实践中经常使用这一策略**。 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/notes/8.0方法分析.md: -------------------------------------------------------------------------------- 1 | 本章介绍用于分析方法代码的 ASM API,它是基于树 API 的。首先介绍代码分析算法,然后以一些示例介绍相应的 ASM API。 -------------------------------------------------------------------------------- /docs/notes/8.1介绍.md: -------------------------------------------------------------------------------- 1 | # 8.1 介绍 2 | 3 | 代码分析是一个很大的主题,存在许多代码分析算法。我们不可能在这里介绍所有这些算法,也超出了本文档的范围。事实上,这一节的目的只是概述 ASM 中使用的算法。关于这一主题的更好介绍,可以在有关编译器的书中找到。接下来的几节将介绍代码分析技术的两个重要类型,即数据流和控制流分析: 4 | 5 | - **数据流**分析包括:对于一个方法的每条指令,计算其执行帧的状态。这一状态可能采用一种多少有些抽象的方式来表示。例如,引用值可能用一个值来表示,可以每个类一个值,可以是{null, 非 null,可为 null}集合中的三个可能值表示,等等。 6 | - **控制流**分析包括计算一个方法的控制流图,并对这个图进行分析。控制流图中的节点为指令,如果指令 j 可以紧跟在 i 之后执行,则图的有向边将连接这两条指令 `i→j`。 7 | 8 | ## 8.1.1 数据流分析 9 | 10 | 有两种类型的数据流分析可以执行: 11 | 12 | - **正向分析**是指对于每条指令,根据执行帧在执行此指令之前的状态,计算执行帧在这一 指令之后的状态。 13 | - **反向分析**是指对于每条指令,根据执行帧在执行此指令之后的状态,计算执行帧在这一 指令之前的状态。 14 | 15 | 正向数据流分析的执行是对于一个方法的每个字节代码指令,模拟它在其执行帧上的执行,通常包括: 16 | 17 | - 从栈中弹出值, 18 | - 合并它们, 19 | - 将结果压入栈中。 20 | 21 | - 这看起来似乎就是解释器或 Java 虚拟机做的事情,但事实上,它是完全不同的,因为其目 标是对于所有可能出现的参数值,模拟一个方法中的所有可能执行路径,而不是由某一组特定方法参数值所决定的单一执行路径。一个结果就是,对于分支指令,两个路径都将被模拟(而实际解释器将会根据实际条件值,仅沿一条分支执行)。 22 | 23 | 另一个结果是,所处理的值实际上是由可能取值组成的集合。这些集合可能非常大,比如“所有可能值”,“所有整数”,“所有可能对象”或者“所有可能的 String 对象”,在这些情况下,可以将它们称为类型。它们也可能更为准确,比如“所有正整数”,“所有介于 0 到 10 之间的整数”,或者“所有不为 null 的可能对象”。要模拟指令 i 的执行,就是要对于其操作数取值集合中的所有组合形式,找出 i 的所有可能结果集。例如,如果整数由以下三个集合表示:P=“正整数或 null”,N=“负整数或 null”,A=“所有整数”,要模拟 IADD 指令,就意味着当两个操作数均为 P 时返回 P,当两个操作数均为 N 时返回 N,在所有其他情况下返回 A。 24 | 25 | 最后一个后果是需要计算取值的并集:例如,与(b ? e1 : e2)对应的可能值集是 e1 的可能值与 e2 的可能值的并集。更一般地说,每当控制流图包含两条或多条具有同一目的地的边时,就需要这一操作。在上面的例子中,整数由三个集合 P、N 和 A 表示,可以很容易地计算出这些集合中两个集合的并集:除非这两个集合相等,否则总是 A。 26 | 27 | ## 8.1.2 控制流分析 28 | 29 | 控制流分析的基础是方法的控制流图。举个例子,3.1.3 节 checkAndSetF 方法的控制流图给出如下(图中包含的标记类似于实际指令): 30 | 31 | ![](http://asm.itstack.org/assets/img/2020/8.1-1.png) 32 | 33 | 这个图可以分解为四个基本模块(如图中的矩形所示),一个基本模块就是这样一个指令序列:除最后一条指令外,每个指令都恰有一个后继者,而且除第一条外,所有其他指令都不是跳转的目标。 -------------------------------------------------------------------------------- /docs/notes/8.2组件与接口.md: -------------------------------------------------------------------------------- 1 | # 8.2 接口与组件 2 | 3 | 用于代码分析的 ASM API 在 `org.objectweb.asm.tree.analysis` 包中。由包的名字可以看出,它是基于树 API 的。事实上,这个包提供了一个进行正向数据流分析的框架。 为了能够以准确度不一的取值进行各种数据流分析,数据流分析算法分为两部分:一种是固定的,由框架提供,另一种是变化的,由用户提供。更准确地说: 4 | 5 | - 整体数据流分析算法、将适当数量的值从栈中弹出和压回栈中的任务仅实现一次,用于 Analyzer 和 Frame 类中的所有内容。 6 | - 合并值的任何和计算值集并集的任务由用户定义的 Interpreter 和 Value 抽象类的子类提供。提供了几个预定义的子类,下面几节将进行介绍。 7 | 8 | 尽管框架的主要目的是执行数据流分析,但 Analyzer 类也可构造所分析方法的控制流图。为此,可以重写这个类的newControlFlowEdge 和newControlFlowExceptionEdge 方法, 它们默认情况下不做任何事情。其结果可用于进行控制流分析。 9 | 10 | ## 8.2.1 基本数据流分析 11 | 12 | Interpreter 类是抽象类中预定义的 Interpreter 子类之一。它利用在 BasicValue 13 | 类中定义的七个值集来模拟字节代码指令的效果: 14 | - UNINITIALIZED_VALUE 指“所有可能值”。 15 | - INT_VALUE 指“所有 int、short、byte、boolean 或 char 值”。 16 | - FLOAT_VALUE 指“所有 float 值”。 17 | - LONG_VALUE 指“所有 long 值”。 18 | - DOUBLE_VALUE 指“所有 double 值”。 19 | - REFERENCE_VALUE 指“所有对象和数组值”。 20 | - RETURNADDRESS_VALUE 用于子例程(见附录 A.2) 21 | 22 | 这个解释器本身不是非常有用(方法帧中已经提供了这一信息,而且更为详细——见 3.1.5 节),但它可以用作一个“空的”Interpreter 实现,以构建一个 Analyzer。这个分析器可用于检测方法中的不可及代码。事实上,即使是沿着跳转指令的两条分支,也不可能到达那些不能由第一条指令到达的代码。其结果是:在分析之后,无论什么样的 Interpreter 实现,由Analyzer.getFrames 方法返回的计算帧,对于不可到达的指令都是 null。这一特性可用于非常轻松地实现一个 RemoveDeadCodeAdapter 类(还有一些更高效的方法,但它们需要编写的代码也更多): 23 | 24 | ```java 25 | public class RemoveDeadCodeAdapter extends MethodVisitor { 26 | String owner; 27 | MethodVisitor next; 28 | 29 | public RemoveDeadCodeAdapter(String owner, int access, String name, String desc, MethodVisitor mv) { 30 | super(ASM4, new MethodNode(access, name, desc, null, null)); 31 | this.owner = owner; 32 | next = mv; 33 | } 34 | 35 | @Override 36 | public void visitEnd() { 37 | MethodNode mn = (MethodNode) mv; 38 | Analyzer a = 39 | new Analyzer(new BasicInterpreter()); 40 | try { 41 | a.analyze(owner, mn); 42 | 43 | Frame[] frames = a.getFrames(); 44 | AbstractInsnNode[] insns = mn.instructions.toArray(); 45 | for (int i = 0; i < frames.length; ++i) { 46 | if (frames[i] == null && !(insns[i] instanceof LabelNode)) { 47 | mn.instructions.remove(insns[i]); 48 | } 49 | } 50 | } catch (AnalyzerException ignored) { 51 | } 52 | mn.accept(next); 53 | } 54 | } 55 | ``` 56 | 57 | 结合 7.1.5 节的 OptimizeJumpAdapter,由跳转优化器引入的死代码被移除。例如,对checkAndSetF 方法应用这个适配器链将给出: 58 | 59 | | 在 OptimizeJump 之后 | 在 RemoveDeadCode 之后 | 60 | |:---|:---| 61 | | ILOAD 1 | ILOAD 1 | 62 | | IFLT label | IFLT label | 63 | | ALOAD 0 | ALOAD 0 | 64 | | ILOAD 1 | ILOAD 1 | 65 | | PUTFIELD ... | PUTFIELD ... | 66 | | RETURN | RETURN | 67 | | label: | label: | 68 | | F_SAME | F_SAME | 69 | | NEW ... | NEW ... | 70 | | DUP | DUP | 71 | | INVOKESPECIAL ... | INVOKESPECIAL ... | 72 | | ATHROW | ATHROW | 73 | | end: | end: | 74 | 75 | 注意,死标记未被移除。这是故意的:它实际上没有改变最终代码,但避免删除一个尽管不可及但可能会在比如 LocalVariableNode 中引用的标记。 76 | 77 | ## 8.2.2 基本数据流验证器 78 | 79 | BasicVerifier 类扩展 BasicInterpreter 类。它使用的事件集相同,但不同于BasicInterpreter 的是,它会验证对指令的使用是否正确。例如,它会验证 IADD 指令的操作数为 INTEGER_VALUE 值(而 BasicInterpreter 只是返回结果,即 INTEGER_VALUE)。这个类可在开发类生成器或适配器时进行调试,见 3.3 节的解释。例如,这个类可以检测出 ISTORE 1 ALOAD 1 序列是无效的。它可以包含在像下面这样一个实用工具适配器中(在实践中,使用 CheckMethodAdapter 类要更简单一些,可以将其配置为使用 BasicVerifier): 80 | 81 | ```java 82 | public class BasicVerifierAdapter extends MethodVisitor { 83 | String owner; 84 | MethodVisitor next; 85 | 86 | public BasicVerifierAdapter(String owner, int access, String name, String desc, MethodVisitor mv) { 87 | super(ASM4, new MethodNode(access, name, desc, null, null)); 88 | this.owner = owner; 89 | next = mv; 90 | } 91 | 92 | @Override 93 | public void visitEnd() { 94 | 95 | MethodNode mn = (MethodNode) mv; 96 | Analyzer a = 97 | new Analyzer a = 127 | new Analyzer(new SimpleVerifier()); 128 | try { 129 | a.analyze(owner, mn); 130 | Frame[] frames = a.getFrames(); 131 | AbstractInsnNode[] insns = mn.instructions.toArray(); 132 | for (int i = 0; i < insns.length; ++i) { 133 | AbstractInsnNode insn = insns[i]; 134 | if (insn.getOpcode() == CHECKCAST) { 135 | Frame f = frames[i]; 136 | if (f != null && f.getStackSize() > 0) { 137 | Object operand = f.getStack(f.getStackSize() - 1); 138 | Class to = getClass(((TypeInsnNode) insn).desc); 139 | Class from = getClass(((BasicValue) operand).getType()); 140 | if (to.isAssignableFrom(from)) { 141 | mn.instructions.remove(insn); 142 | } 143 | } 144 | } 145 | 146 | } 147 | } catch (AnalyzerException ignored) { 148 | } 149 | return mt == null ? mn : mt.transform(mn); 150 | } 151 | 152 | private static Class getClass(String desc) { 153 | try { 154 | return Class.forName(desc.replace(’ /’, ’.’)); 155 | } catch (ClassNotFoundException e) { 156 | throw new RuntimeException(e.toString()); 157 | } 158 | } 159 | 160 | private static Class getClass(Type t) { 161 | if (t.getSort() == Type.OBJECT) { 162 | return getClass(t.getInternalName()); 163 | } 164 | return getClass(t.getDescriptor()); 165 | } 166 | } 167 | ``` 168 | 169 | 但对于 Java 6 类(或者用 COMPUTE_FRAMES 升级到 Java 6 的类),用 AnalyzerAdapter 以核心 API 来完成这一任务要更简单一些,效率要高得多: 170 | 171 | ```java 172 | public class RemoveUnusedCastAdapter extends MethodVisitor { 173 | public AnalyzerAdapter aa; 174 | 175 | public RemoveUnusedCastAdapter(MethodVisitor mv) { 176 | super(ASM4, mv); 177 | } 178 | 179 | @Override 180 | public void visitTypeInsn(int opcode, String desc) { 181 | if (opcode == CHECKCAST) { 182 | Class to = getClass(desc); 183 | if (aa.stack != null && aa.stack.size() > 0) { 184 | Object operand = aa.stack.get(aa.stack.size() - 1); 185 | if (operand instanceof String) { 186 | Class from = getClass((String) operand); 187 | if (to.isAssignableFrom(from)) { 188 | return; 189 | } 190 | } 191 | } 192 | } 193 | mv.visitTypeInsn(opcode, desc); 194 | } 195 | 196 | private static Class getClass(String desc) { 197 | try { 198 | return Class.forName(desc.replace(’ /’, ’.’)); 199 | } catch (ClassNotFoundException e) { 200 | throw new RuntimeException(e.toString()); 201 | } 202 | } 203 | } 204 | ``` 205 | 206 | ## 8.2.4 用户定义的数据流分析 207 | 208 | 假定我们希望检测出一些字段访问和方法调用的对象可能是 null,比如在下面的源代码段中(其中,第一行防止一些编译器检测 Bug,否则它可能会被认作一个“o 可能尚未初始化”错误): 209 | 210 | ```java 211 | Object o = null; while (...) { 212 | o = ...; 213 | } 214 | o.m(...); // 潜在的 NullPointerException! 215 | ``` 216 | 217 | 于是我们需要一个数据流分析,它能告诉我们,在对应于最后一行的 INVOKEVIRTUAL 指令处,与 o 对应的底部栈值可能为 null。为此,我们需要为引用值区分三个集合:包含 null 值的 NULL 集,包含所有非 null 引用值的 NONNULL 集,以及包含所有引用值的 MAYBENULL 集。于是,我们只需要考虑 ACONST_NULL 将 NULL 集压入操作数栈,而所有其他在栈中压入引用值的指令将压入 NONNULL 集(换句话说,我们考虑任意字段访问或方法调用的结果都不是null,如果不对程序的所有类进行全局分析,那就不可能得到更好的结果)。为表示 NULL 和NONNULL 集的并集,MAYBENULL 集合是必需的。 218 | 219 | 上述规则必须在一个自定义的 Interpreter 子类中实现。完全可以从头实现它,但也可以通过扩展 BasicInterpreter 类来实现它,而且这种做法要容易得多。事实上,如果我们考虑 BasicValue.REFERENCE_VALUE 对应于 NONNULL 集,那只需重写模拟 ACONST_NULL 执行的方法,使它返回 NULL,还有计算并集的方法: 220 | 221 | ```java 222 | class IsNullInterpreter extends BasicInterpreter { 223 | public final static BasicValue NULL = new BasicValue(null); 224 | public final static BasicValue MAYBENULL = new BasicValue(null); 225 | 226 | public IsNullInterpreter() { 227 | super(ASM4); 228 | } 229 | 230 | @Override 231 | public BasicValue newOperation(AbstractInsnNode insn) { 232 | if (insn.getOpcode() == ACONST_NULL) { 233 | return NULL; 234 | } 235 | return super.newOperation(insn); 236 | } 237 | 238 | @Override 239 | public BasicValue merge(BasicValue v, BasicValue w) { 240 | if (isRef(v) && isRef(w) && v != w) { 241 | return MAYBENULL; 242 | } 243 | return super.merge(v, w); 244 | } 245 | 246 | private boolean isRef(Value v) { 247 | return v == REFERENCE_VALUE || v == NULL || v == MAYBENULL; 248 | } 249 | } 250 | ``` 251 | 252 | 于是,可以很容易地利用这个 IsNullnterpreter 来检测那些可能导致潜在 null 指针异常的指令: 253 | 254 | ```java 255 | public class NullDereferenceAnalyzer { 256 | public List findNullDereferences(String owner, MethodNode mn) throws AnalyzerException { 257 | List result = new ArrayList(); 258 | Analyzer a = 259 | new Analyzer(new IsNullInterpreter()); 260 | a.analyze(owner, mn); 261 | Frame[] frames = a.getFrames(); 262 | AbstractInsnNode[] insns = mn.instructions.toArray(); 263 | for (int i = 0; i < insns.length; ++i) { 264 | AbstractInsnNode insn = insns[i]; 265 | if (frames[i] != null) { 266 | 267 | Value v = getTarget(insn, frames[i]); 268 | if (v == NULL || v == MAYBENULL) { 269 | result.add(insn); 270 | } 271 | } 272 | } 273 | return result; 274 | } 275 | 276 | private static BasicValue getTarget(AbstractInsnNode insn, Frame f) { 277 | switch (insn.getOpcode()) { 278 | case GETFIELD: 279 | case ARRAYLENGTH: 280 | case MONITORENTER: 281 | case MONITOREXIT: 282 | return getStackValue(f, 0); 283 | case PUTFIELD: 284 | return getStackValue(f, 1); 285 | case INVOKEVIRTUAL: 286 | case INVOKESPECIAL: 287 | case INVOKEINTERFACE: 288 | String desc = ((MethodInsnNode) insn).desc; 289 | return getStackValue(f, Type.getArgumentTypes(desc).length); 290 | } 291 | return null; 292 | } 293 | 294 | private static BasicValue getStackValue(Frame f, int index) { 295 | int top = f.getStackSize() - 1; 296 | return index <= top ? f.getStack(top - index) : null; 297 | } 298 | } 299 | ``` 300 | 301 | **findNullDereferences** 方法用一个 IsNullInterpreter 分析给定方法节点。然后, 对于每条指令,检测其引用操作数(如果有的话)的可能值集是不是 NULL 集或 NONNULL 集。若是,则这条指令可能导致一个 null 指针异常,将它添加到此类指令的列表中,该列表由这一方法返回。 302 | 303 | **getTarget** 方法在帧 f 中返回与 insn 对象操作数相对应的 Value,如果 insn 没有对象操作数,则返回 null。它的主要任务就是计算这个值相对于操作数栈顶端的偏移量,这一数量取决于指令类型。 304 | 305 | ## 8.2.5 控制流分析 306 | 307 | 控制流分析可以有许多应用。一个简单的例子就是计算方法的“圆复杂度”。这一度量定义为控制流图的边数减去节点数,再加上 2。例如,checkAndSetF 方法的控制流图如 8.1.2 节所示,它的圈复杂度为 11-12+2=1。这个度量很好地表征了一个方法的“复杂度”(在这个数字与方法的平均 bug 数之间存在一种关联)。它还给出了要“正确”测试一个方法所需要的建议测试情景数目。 308 | 309 | 用于计算这一度量的算法可以用 ASM 分析框架来实现(还有仅基于核心 API 的更高效方法, 只是它们需要编写更多的代码)。第一步是构建控制流图。我们在本章开头曾经说过,可以通过重写 Analyzer 类的 newControlFlowEdge 方法来构建。这个类将节点表示为 Frame 对象。 如果希望将这个图存储在这些对象中,则需要扩展 Frame 类: 310 | 311 | ```java 312 | class Node extends Frame { 313 | Set> successors = new HashSet>(); 314 | 315 | public Node(int nLocals, int nStack) { 316 | super(nLocals, nStack); 317 | } 318 | 319 | public Node(Frame src) { 320 | super(src); 321 | } 322 | } 323 | ``` 324 | 325 | 随后,可以提供一个 Analyzer 子类,用来构建控制流图,并用它的结果来计算边数、节点数,最终计算出圈复杂度: 326 | 327 | ```java 328 | public class CyclomaticComplexity { 329 | public int getCyclomaticComplexity(String owner, MethodNode mn) throws AnalyzerException { 330 | Analyzer a = 331 | new Analyzer(new BasicInterpreter()) { 332 | protected Frame newFrame(int nLocals, int nStack) { 333 | return new Node(nLocals, nStack); 334 | } 335 | 336 | protected Frame newFrame(Frame src) { 337 | return new Node(src); 338 | } 339 | 340 | protected void newControlFlowEdge(int src, int dst) { 341 | Node s = (Node) getFrames()[src]; 342 | s.successors.add((Node) getFrames()[dst]); 343 | } 344 | }; 345 | a.analyze(owner, mn); 346 | Frame[] frames = a.getFrames(); 347 | int edges = 0; 348 | int nodes = 0; 349 | for (int i = 0; i < frames.length; ++i) { 350 | if (frames[i] != null) { 351 | edges += ((Node) frames[i]).successors.size(); 352 | nodes += 1; 353 | } 354 | } 355 | return edges - nodes + 2; 356 | } 357 | } 358 | ``` 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | -------------------------------------------------------------------------------- /docs/notes/9.0元数据.md: -------------------------------------------------------------------------------- 1 | 本章介绍用于编译 Java 类元数据(比如注释)的树 API。本章非常短,因为这些元数据已经在第 4 章介绍过了,而且在了解了相应的核心 API 之后,树 API 就很简单了。 -------------------------------------------------------------------------------- /docs/notes/9.1泛型.md: -------------------------------------------------------------------------------- 1 | 树 API 没有提供对泛型的任何支持!事实上,它用签名表示泛型,这一点与核心 API 中一样,但却没有提供与 SignatureVisitor 对应的 SignatureNode 类,尽管这也是可能的(事实上,至少使用几个 Node 类来区分类型、方法和类签名会很方便)。 -------------------------------------------------------------------------------- /docs/notes/9.2注释.md: -------------------------------------------------------------------------------- 1 | # 9.2 注释 2 | 3 | 注释的树 API 都基于 AnnotationNode 类,它的公共 API 如下: 4 | 5 | ```java 6 | public class AnnotationNode extends AnnotationVisitor { 7 | public String desc; 8 | public List values; 9 | 10 | public AnnotationNode(String desc); 11 | 12 | public AnnotationNode(int api, String desc); 13 | ... // AnnotationVisitor 接口的方法 14 | 15 | public void accept(AnnotationVisitor av); 16 | } 17 | ``` 18 | 19 | desc 字段包含了注释类型,而 values 字段包含了名称/值对,其中每个名字后面都跟有相关联的值(值的表示在 Javadoc 中描述)。 20 | 21 | 可以看出,AnnotationNode 类扩展了 AnnotationVisitor 类,还提供了一个 accept 方法,它以一个这种类型的对象为参数,比如具有这个类和方法访问器类的 ClassNod 和MethodNode 类。我们前面已经看到用于类和方法的模式也可用于合成处理注释的核心与树 API 组件。例如,对于基于继承的模式(见 7.2.2 节),可进行“匿名内部类”的变体,使其适用于注释,给出如下: 22 | 23 | ```java 24 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 25 | return new AnnotationNode(ASM4, desc) { 26 | @Override 27 | public void visitEnd() { 28 | // 将注释转换代码放在这里 29 | accept(cv.visitAnnotation(desc, visible)); 30 | } 31 | }; 32 | } 33 | ``` -------------------------------------------------------------------------------- /docs/notes/9.3调试.md: -------------------------------------------------------------------------------- 1 | # 9.3 调试 2 | 3 | 作为被编译类来源的源文件存储在 ClassNode 中的 sourceFile 字段中。关于源代码行号的信息存储在 LineNumberNode 对象中,它的类继承自 AbstractInsnNode。在核心 API 中,关于行号的信息是与指令同时受访问的,与此类似,LineNumberNode 对象是指令列表的一部分。最后,源局部变量的名字和类型存储在 MethodNode 的 localVariables 字段中, 它是 LocalVariableNode 对象的一个列表。 -------------------------------------------------------------------------------- /docs/notes/A.0附录.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyGitBooks/asm/1cdddec77126a55bf4a609e0c09eb2198966376c/docs/notes/A.0附录.md -------------------------------------------------------------------------------- /docs/notes/A.1字节代码指.md: -------------------------------------------------------------------------------- 1 | # A.1 字节代码指令 2 | 3 | 本节对字节代码指令进行简要描述。如需全面描述,请参阅 Java 虚拟机规范。 4 | 5 | 约定:a 和 b 表示 int, float, long 或 double 值(比如,它们对于 IADD 表示 int,而对于 LADD 则表示 long),o 和 p 表示对象引用,v 表示任意值(或者,对于栈指令,表示大小为 1 的值),w 表示 long 或 double,i、j 和 n 表示 int 值。 6 | 7 | >局部变量 8 | 9 | | 指令 | 之前的栈 | 之后的栈 | 10 | |:---|:---|:---| 11 | | ILOAD, LLOAD, FLOAD, DLOAD var | ... | ... , a | 12 | | ALOAD var | ... | ... , o | 13 | | ISTORE, LSTORE, FSTORE, DSTORE var | ... , a | ...| 14 | | ASTORE var | ... , o| ... | 15 | | IINC var incr | ... | ... | 16 | 17 | >栈 18 | 19 | | 指令 | 之前的栈 | 之后的栈 | 20 | |:---|:---|:---| 21 | | POP | ... , v | ... | 22 | | POP2 | ... , v1 , v2 | ... | 23 | | POP2 | ... , w | ... | 24 | | DUP | ... , v | ... , v , v | 25 | | DUP2 | ... , v1 , v2 | ... , v1 , v2 , v1 , v2 | 26 | | DUP2 | ... , w | ... , w, w | 27 | | SWAP | ... , v1 , v2 | ... , v2 , v1 | 28 | | DUP_X1 | ... , v1 , v2 | ... , v2 , v1 , v2 | 29 | | DUP_X2 | ... , v1 , v2 , v3 | ... , v3 , v1 , v2 , v3 | 30 | | DUP_X2 | ... , w , v | ... , v , w , v | 31 | | DUP2_X1 | ... , v1 , v2 , v3 | ... , v2 , v3 , v1 , v2 , v3 | 32 | | DUP2_X1 | ... , v , w | ... , w , v , w | 33 | | DUP2_X2 | ... , v1 , v2 , v3 , v4 | ... , v3 , v4 , v1 , v2 , v3 , v4 | 34 | | DUP2_X2 | ... , w , v1 , v2 | ... , v1 , v2 , w , v1 , v2 | 35 | | DUP2_X2 |.... , v1 , v2 , w | ... , w , v1 , v2 , w| 36 | | DUP2_X2 |... , w1 , w2 | ... , w2 , w1 , w2 | 37 | 38 | >常量 39 | 40 | | 指令 | 之前的栈 | 之后的栈 | 41 | |:---|:---|:---| 42 | | ICONST_n (−1 _ n _ 5) | ... | ... , n | 43 | | LCONST_n (0 _ n _ 1) | ... | ... , nL | 44 | | FCONST_n (0 _ n _ 2) | ... | ... , nF | 45 | | DCONST_n (0 _ n _ 1) | ... | ... , nD | 46 | | BIPUSH b, −128 _ b < 127 | ... | ... , b | 47 | | SIPUSH s, −32768 _ s < 32767 | ... | ... , s | 48 | | LDC cst (int, float, long, double, String 或 Type) | ... | ... , cst | 49 | | ACONST_NULL | ... | ... , null | 50 | 51 | >算数与逻辑 52 | 53 | | 指令 | 之前的栈 | 之后的栈 | 54 | |:---|:---|:---| 55 | | IADD, LADD, FADD, DADD | ... , a , b | ... , a + b | 56 | | ISUB, LSUB, FSUB, DSUB | ... , a , b | ... , a - b | 57 | | IMUL, LMUL, FMUL, DMUL | ... , a , b | ... , a * b | 58 | | IDIV, LDIV, FDIV, DDIV | ... , a , b | ... , a / b | 59 | | IREM, LREM, FREM, DREM | ... , a , b | ... , a % b | 60 | | INEG, LNEG, FNEG, DNEG | ... , a | ... , -a | 61 | | ISHL, LSHL | ... , a , n | ... , a <_< n | 62 | | ISHR, LSHR | ... , a , n | ... , a >_> n | 63 | | IUSHR, LUSHR | ... , a , n | ... , a >_>_> n | 64 | | IAND, LAND | ... , a , b | ... , a & b | 65 | | IOR, LOR | ... , a , b | ... , a | b | 66 | | IXOR, LXOR | ... , a , b | ... , a ^ b | 67 | | LCMP | ... , a , b | ... , a == b ? 0 : (a < b ? -1 : 1) | 68 | | FCMPL, FCMPG | ... , a , b | ... , a == b ? 0 : (a < b ? -1 : 1) | 69 | | DCMPL, DCMPG | ... , a , b | ... , a == b ? 0 : (a < b ? -1 : 1) | 70 | 71 | >类型转换 72 | 73 | | 指令 | 之前的栈 | 之后的栈 | 74 | |:---|:---|:---| 75 | | I2B | ... , i | ... , (byte) i 76 | | I2C | ... , i | ... , (char) i 77 | | I2S | ... , i | ... , (short) i 78 | | L2I, F2I, D2I | ... , a | ... , (int) a 79 | | I2L, F2L, D2L | ... , a | ... , (long) a 80 | | I2F, L2F, D2F | ... , a | ... , (float) a 81 | | I2D, L2D, F2D | ... , a | ... , (double) a 82 | | CHECKCAST class | ... , o | ... , (class) o 83 | 84 | >对象、字段和方法 85 | 86 | | 指令 | 之前的栈 | 之后的栈 | 87 | |:---|:---|:---| 88 | | NEW class … | …,new class | | 89 | | GETFIELD c f t | ... , o | ... , o.f | 90 | | PUTFIELD c f t | ... , o , v | ... | 91 | | GETSTATIC c f t | ... | ... , c.f | 92 | | PUTSTATIC c f t | ... , v | ... | 93 | | INVOKEVIRTUAL c m t | ... , o , v1 , ... , vn | ... , o.m(v1, ... vn) | 94 | | INVOKESPECIAL c m t | ... , o , v1 , ... , vn | ... , o.m(v1, ... vn) | 95 | | INVOKESTATIC c m t | ... , v1 , ... , vn | ... , c.m(v1, ... vn) | 96 | | INVOKEINTERFACE c m t | ... , o , v1 , ... , vn | ... , o.m(v1, ... vn) | 97 | | INVOKEDYNAMIC m t bsm | ... , o , v1 , ... , vn | ... , o.m(v1, ... vn) | 98 | | INSTANCEOF class | ... , o | ... , o instanceof class | 99 | | MONITORENTER | ... , o | ... | 100 | | MONITOREXIT | ... , o | ... | 101 | 102 | >数组 103 | 104 | | 指令 | 之前的栈 | 之后的栈 | 105 | |:---|:---|:---| 106 | | NEWARRAY type (用于任意基元类型) | ... , n | ... , new type[n] | 107 | | ANEWARRAY class | ... , n | ... , new class[n] | 108 | | MULTIANEWARRAY [...[t n | ... , i1 ,... , in | ... , new t[i1]...[in]… | 109 | | BALOAD, CALOAD, SALOAD | ... , o , i | ... , o[i] | 110 | | IALOAD, LALOAD,FALOAD, DALOAD | ... , o , i | ... , o[i] | 111 | | AALOAD | ... , o , i | ... , o[i] | 112 | | BASTORE, CASTORE, SASTORE | ... , o , i , j | ... | 113 | | IASTORE, LASTORE, FASTORE, DASTORE | ... , o , i , a | ... | 114 | | AASTORE | ... , o , i , p | ... | 115 | | ARRAYLENGTH | ... , o | ... , o.length | 116 | 117 | >跳转 118 | 119 | | 指令 | 之前的栈 | 之后的栈 | 120 | |:---|:---|:---| 121 | | IFEQ | ... , i | ... i == 0 时跳转 | 122 | | IFNE | ... , i | ... i != 0 时跳转 | 123 | | IFLT | ... , i | ... i < 0 时跳转 | 124 | | IFGE | ... , i | ... i >= 0 时跳转 | 125 | | IFGT | ... , i | ... i > 0 时跳转 | 126 | | IFLE | ... , i | ... i <= 0 时跳转 | 127 | | IF_ICMPEQ | ... , i , j | ... i == j 时跳转 | 128 | | IF_ICMPNE | ... , i , j | ... i != j 时跳转 | 129 | | IF_ICMPLT | ... , i , j | ... i < j 时跳转 | 130 | | IF_ICMPGE | ... , i , j | ... i >= j 时跳转 | 131 | | IF_ICMPGT | ... , i , j | ... i > j 时跳转 | 132 | | IF_ICMPLE | ... , i , j | ... i <= j 时跳转 | 133 | | IF_ACMPEQ | ... , o , p | ... o == p 时跳转 | 134 | | IF_ACMPNE | ... , o , p | ... o != p 时跳转 | 135 | | IFNULL | ... , o | ... o == null 时跳转 | 136 | | IFNONNULL | ... , o | ... o != null 时跳转 | 137 | | GOTO | ... | ... 总是跳转 | 138 | | TABLESWITCH | ... , i | ... 总是跳转 | 139 | | LOOKUPSWITCH | ... , i | ... 总是跳转 | 140 | 141 | >返回 142 | 143 | | 指令 | 之前的栈 | 144 | |:---|:---| 145 | | IRETURN, LRETURN, FRETURN, DRETURN | ... , a | 146 | | ARETURN | ... , o | 147 | | RETURN | ... | 148 | | ATHROW | ... , o | 149 | -------------------------------------------------------------------------------- /docs/notes/A.2子例程.md: -------------------------------------------------------------------------------- 1 | 除了上一节给出的字节代码指令,版本号低于或等于 V1_5 的类还可以包含 JSR 和 RET 指令,用于子例程(JSR 表示 Jump to SubRoutine,即跳转至子例程,RET 表示 RETurn from subroutine,,即从子例程返回)。版本高于或等于 V1_6 的类不能包含这些指令(移除它们就是为了 Java 6 中引入的新验证器体系结构;因为它们不是严格必需的,所以才可能删除它们)。 2 | 3 | JSR 指令以一个标记为参数,无条件跳转到这个标记。但在跳转之前,它会在操作数栈中压入一个返回地址,它是紧跟在 JSR 之后的指令的索引。这个返回地址只能由诸如 POP、DUP 或SWAP 之类的栈指令、ASTORE 指令和 RET 指令处理。 4 | 5 | RET 指令以一个局部变量索引为参数。它加载包含在这个槽中的返回地址,并无条件跳转至相应的指令。由于返回地址可以有几个可能值,所以 RET 指令可以返回到几个可能指令。 让我们用一个例子来说明。考虑以下代码: 6 | 7 | ```java 8 | JSR sub JSR sub RETURN 9 | sub: 10 | ASTORE 1 11 | IINC 0 1 12 | RET 1 13 | ``` 14 | 15 | 第一条指令将第二条指令的索引作为返回地址压入栈中,并跳转到 ASTORE 指令。这个指令将返回地址存储局部变量 1 中。然后,局部变量 0 增 1。最后,RET 指令载入在局部变量 1 中包含的返回地址,并跳转到相应的指令,即第二条指令。 16 | 17 | 这个第二指令又是一个 JSR 指令:它将第三条指令的索引作为返回地址压入栈中,并跳转到 ASTORE 指令。当再次到达 RET 指令时,返回地址现在对应于 RETURN 指令,所以执行过程跳转到这个 RETURN,并停止。 18 | 19 | sub 标记之后的指令定义了一个所谓的子例程。它有点像“方法”,可以从一个正常方法的不同地方“调用”。在 Java 6 之前,子例程用于编译 Java 中的 finally 块。但事实上,子例程并非必不可少的:实际上,有可能用相应子例程的主体来代替每个 JSR 指令。这种内联生成了重复代码,但删除了 JSR 和 RET 指令。对于上面的例子,结果非常简单: 20 | 21 | ```java 22 | IINC 0 1 23 | IINC 0 1 RETURN 24 | ``` 25 | 26 | ASM 在 org.objectweb.asm.commons 包中提供了一个 JSRInlinerAdapter 类,它可以自动执行这一转换。可以用它来删除 JSR 和 RET 指令,以简化代码分析,或者将类从 1.5 或更低版本转换为 1.6 或更高版本。 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/notes/A.3属性.md: -------------------------------------------------------------------------------- 1 | 2.1.1 节曾经解释过,有可能将任意属性关联到类、字段和方法。在引入新特性时,这种可扩展机制对于扩展类文件格式非常有用。例如,它已经被用于扩展这一格式,以支持注释、泛型、栈映射帧等。这一机制还可由用户使用,而不只是由 Sun 公司使用,但自从在 Java 5 中引入注释之后,注释的使用就比属性容易得多。也就是说,如果你真的需要使用自己的属性,或者必须管理由别人定义的非标准属性,可以在 ASM 用 Attribute 类完成。 2 | 3 | 默认情况下,ClassReader 类会为它找到的每个标准属性创建一个 Attribute 实例,并以这个实例为参数,调用 visitAttribute 方法(至于是 ClassVisitor、FieldVisitor, 还是 MethodVisitor 类的该方法,则取决于上下文)。这个实例中包含了属性的原始内容,其形式为私有字节数组。在访问这种未知属性时,ClassWriter 类就是将这个原始字节数组复制到它构建的类中。这一默认行为只有在使用 2.2.4 节介绍的优化时才是安全的(除了提高性能外,这是使用该优化的另一原因)。没有这一选项,原内容可能会与类编写器创建的新常量池不一致, 从而导致类文件被损坏。 4 | 5 | 默认情况下,非标准属性会以它在已转换类中的形式被复制,它的内容对 ASM 和用户来说是完全不透明的。如果需要访问这一内容,必须首先定义一个 Attribute 子类,能够对原内容进行解码并重新编码。还必须在 ClassReader.accept 方法中传送这个类的一个原型实例,使这个类可以解码这一类型的属性。让我们用一个例子来说明这一点。下面的类可用于运行一个设想的“注释”特性,它的原始内容是一个 short 值,引用存储在常量池中的一个 UTF8 字符串: 6 | 7 | ```java 8 | class CommentAttribute extends Attribute { 9 | private String comment; 10 | 11 | public CommentAttribute(final String comment) { 12 | super("Comment"); 13 | this.comment = comment; 14 | } 15 | 16 | public String getComment() { 17 | return comment; 18 | } 19 | 20 | @Override 21 | public boolean isUnknown() { 22 | 23 | return false; 24 | } 25 | 26 | @Override 27 | protected Attribute read(ClassReader cr, int off, int len, char[] buf, int codeOff, Label[] labels) { 28 | return new CommentAttribute(cr.readUTF8(off, buf)); 29 | } 30 | 31 | @Override 32 | protected ByteVector write(ClassWriter cw, byte[] code, int len, int maxStack, int maxLocals) { 33 | return new ByteVector().putShort(cw.newUTF8(comment)); 34 | } 35 | } 36 | ``` 37 | 38 | 最重要的方法是 read 和 write 方法。read 方法对这一类型的属性的原始内容进行解码, write 方法执行逆操作。注意,read 方法必须返回一个新的属性实例。为了在读取一个类时实现这种属性的解码,必须使用: 39 | 40 | ```java 41 | ClassReader cr = ...; ClassVisitor cv = ...; 42 | cr.accept(cv, new Attribute[] { new CommentAttribute("") }, 0); 43 | ``` 44 | 45 | 这个“注释”属性将被识别,并为它们中的每一个都创建一个 CommentAttribute 实例(而未知属性仍将用 Attribute 实例表示)。 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/notes/A.4规则.md: -------------------------------------------------------------------------------- 1 | # A.4 规则 2 | 3 | 现在回忆一下为确保你的代码能与较早的 ASM 版本保持后向兼容性而必须遵循的规则(见第 5 章和第 10 章)。 4 | 5 | - 规则 1:要为 ASM X 编写一个 ClassVisitor 子类,就以这个版本号为参数,调用ClassVisitor 构造器,在这个版本的 ClassVisitor 类中,绝对不要重写或调用被弃用的方法(或者将在之后版本引入的方法)。 6 | - 规则 2:不要使用访问器的继承,而要使用委托(即访问器链)。一种好的做法是让你的访问器类在默认情况下成为 final 的,以确保这一特性。 7 | - 规则 3:要用 ASM 版本 X 的树 API 编写类分析器或适配器,则使用以这一确切版本为参数的构造器创建 ClassNode(而不是使用没有参数的默认构造器)。 8 | - 规则 4:要用 ASM 版本 X 的树 API 编写一个类分析器或适配器,使用别人创建的ClassNode,在以任何方式使用这个 ClassNode 之前,都要以这个确切版本号为参数,调用它的 check()方法。 9 | 10 | 规则 1 和 2 还适用于 ClassNode、MethodNode 等的子类,asm.tree.analysis 中Interpreter 及其子类的子类,asm.util 中 ASMifier、Texifier 或 CheckXxx Adapter类的子类,asm.commons 包中任意类的子类。最后,规则 2 有两个例外: 11 | 12 | - 如果能够完全由自己控制继承链,并同时发布层次结构中的所有类,那就可以使用访问器的继承。然后必须确保层次结构中的所有类都是为同一 ASM 版本编写的。仍然要让 层次结构的叶类是 final 的。 13 | - 如果除了叶类之外,没有其他类重写任何访问方法(例如,如果只是为了引入方便的方法而在 ClassVisitor 和具体访问类之间使用了中间类),那就可以使用“访问器”的继承。仍然要让层次结构的叶类是 final 的(除非它们也没有重写任何访问方法;在这种情况下,提供一个以 ASM 版本为参数的构造器,使子类可以指定它们是为哪个版本编写的)。 14 | 15 | -------------------------------------------------------------------------------- /docs/notes/A.5性能.md: -------------------------------------------------------------------------------- 1 | # A.5 性能 2 | 3 | 下图给出核心与树 API、ClassWriter 选项和分析框架的相对性能(越短越快): 4 | 5 | ![](http://asm.itstack.org/assets/img/2020/A.5-1.png) 6 | 7 | 引用时间 100 对应于直接链接到 ClassWriter 的 ClassReader。“加计时器”和“移除序列”测试对应于 AddTimerAdapter 和 RemoveGetFieldPutFieldAdapter(斜体表示使用 2.2.4 节所述的优化,粗体表示使用树 API)。总转换时间分解为三个部分:类分析(下)、类转换或分析(中间)和类的写入(上)。对于每个测试,测量值都是分析、转换和写入一个字节数组所需要的时间,也就是从磁盘加载类并将它们加载到 JVM 所需要的时间未考虑在内。为 获得这些结果,将每个测试对于 JDK 7 rt.jar 上的 18600 多个类运行 10 次,并采用最佳运行时获得的性能。 8 | 9 | 快速分析一下这些结果表明: 10 | 11 | - 90%的转换时间用于类分析和写入。 12 | - “复制常量池”优化可提速 15-20%。 13 | - 基于树的转换要比基于访问器的慢大约 25%。 14 | - COMPUTE_MAXS 选项不会耗时太多。 15 | - COMPUTE_FRAMES 选项耗时很多⇒进行增量帧更新。 16 | - 分析包的成本非常高! 17 | -------------------------------------------------------------------------------- /docs/notes/JVM-指令表.md: -------------------------------------------------------------------------------- 1 | >常量入栈指令 2 | 3 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 4 | | :----------|:-------------| :-----| :-----| 5 | | 0x01 | aconst_null | | null值入栈。 | 6 | | 0x02 | iconst_m1 | | -1(int)值入栈。 | 7 | | 0x03 | iconst_0 | | 0(int)值入栈。 | 8 | | 0x04 | iconst_1 | | 1(int)值入栈。 | 9 | | 0x05 | iconst_2 | | 2(int)值入栈。 | 10 | | 0x06 | iconst_3 | | 3(int)值入栈。 | 11 | | 0x07 | iconst_4 | | 4(int)值入栈。 | 12 | | 0x08 | iconst_5 | | 5(int)值入栈。 | 13 | | 0x09 | lconst_0 | | 0(long)值入栈。 | 14 | | 0x0a | lconst_1 | | 1(long)值入栈。 | 15 | | 0x0b | fconst_0 | | 0(float)值入栈。 | 16 | | 0x0c | fconst_1 | | 1(float)值入栈。 | 17 | | 0x0d | fconst_2 | | 2(float)值入栈。 | 18 | | 0x0e | dconst_0 | | 0(double)值入栈。 | 19 | | 0x0f | dconst_1 | | 1(double)值入栈。 | 20 | | 0x10 | bipush | valuebyte | valuebyte值带符号扩展成int值入栈。 | 21 | | 0x11 | sipush | valuebyte1 valuebyte2 | (valuebyte1 << 8) valuebyte2 值带符号扩展成int值入栈。 | 22 | | 0x12 | ldc | indexbyte1 | 常量池中的常量值(int, float, string reference, object reference)入栈。 | 23 | | 0x13 | ldc_w | indexbyte1 indexbyte2| 常量池中常量(int, float, string reference, object reference)入栈。 | 24 | | 0x14 | ldc2_w | indexbyte1 indexbyte2 | 常量池中常量(long, double)入栈。 | 25 | 26 | 27 | >局部变量值转载到栈中指令 28 | 29 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 30 | | :----------|:-------------| :-----| :-----| 31 | | 0x19 | (wide)aload | indexbyte | 从局部变量indexbyte中装载引用类型值入栈。 | 32 | | 0x2a | aload_0 | | 从局部变量0中装载引用类型值入栈。 | 33 | | 0x2b | aload_1 | | 从局部变量1中装载引用类型值入栈。 | 34 | | 0x2c | aload_2 | | 从局部变量2中装载引用类型值入栈。 | 35 | | 0x2d | aload_3 | | 从局部变量3中装载引用类型值入栈。 | 36 | | 0x15 | (wide)iload | indexbyte | 从局部变量indexbyte中装载int类型值入栈。 | 37 | | 0x1a | iload_0 | | 从局部变量0中装载int类型值入栈。 | 38 | | 0x1b | iload_1 | | 从局部变量1中装载int类型值入栈。 | 39 | | 0x1c | iload_2 | | 从局部变量2中装载int类型值入栈。 | 40 | | 0x1d | iload_3 | | 从局部变量3中装载int类型值入栈。 | 41 | | 0x16 | (wide)lload | indexbyte | 从局部变量indexbyte中装载long类型值入栈。 | 42 | | 0x1e | lload_0 | | 从局部变量0中装载int类型值入栈。 | 43 | | 0x1f | lload_1 | | 从局部变量1中装载int类型值入栈。 | 44 | | 0x20 | lload_2 | | 从局部变量2中装载int类型值入栈。 | 45 | | 0x21 | lload_3 | | 从局部变量3中装载int类型值入栈。 | 46 | | 0x17 | (wide)fload | indexbyte | 从局部变量indexbyte中装载float类型值入栈。 | 47 | | 0x22 | fload_0 | | 从局部变量0中装载float类型值入栈。 | 48 | | 0x23 | fload_1 | | 从局部变量1中装载float类型值入栈。 | 49 | | 0x24 | fload_2 | | 从局部变量2中装载float类型值入栈。 | 50 | | 0x25 | fload_3 | | 从局部变量3中装载float类型值入栈。 | 51 | | 0x18 | (wide)dload | indexbyte | 从局部变量indexbyte中装载double类型值入栈。 | 52 | | 0x26 | dload_0 | | 从局部变量0中装载double类型值入栈。 | 53 | | 0x27 | dload_1 | | 从局部变量1中装载double类型值入栈。 | 54 | | 0x28 | dload_2 | | 从局部变量2中装载double类型值入栈。 | 55 | | 0x29 | dload_3 | | 从局部变量3中装载double类型值入栈。 | 56 | | 0x32 | aaload | | 从引用类型数组中装载指定项的值。 | 57 | | 0x2e | iaload | | 从int类型数组中装载指定项的值。 | 58 | | 0x2f | laload | | 从long类型数组中装载指定项的值。 | 59 | | 0x30 | faload | | 从float类型数组中装载指定项的值。 | 60 | | 0x31 | daload | | 从double类型数组中装载指定项的值。 | 61 | | 0x33 | baload | | 从boolean类型数组或byte类型数组中装载指定项的值(先转换为int类型值,后压栈)| 62 | | 0x34 | caload | | 从char类型数组中装载指定项的值(先转换为int类型值,后压栈)。 | 63 | | 0x35 | saload | | 从short类型数组中装载指定项的值(先转换为int类型值,后压栈)。 | 64 | 65 | 66 | >将栈顶值保存到局部变量中指令 67 | 68 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 69 | | :----------|:-------------| :-----| :-----| 70 | | 0x3a | (wide)astore | indexbyte | 将栈顶引用类型值保存到局部变量indexbyte中。 | 71 | | 0x4b | astroe_0 | | 将栈顶引用类型值保存到局部变量0中。 | 72 | | 0x4c | astore_1 | | 将栈顶引用类型值保存到局部变量1中。 | 73 | | 0x4d | astore_2 | | 将栈顶引用类型值保存到局部变量2中。 | 74 | | 0x4e | astore_3 | | 将栈顶引用类型值保存到局部变量3中。 | 75 | | 0x36 | (wide)istore | indexbyte | 将栈顶int类型值保存到局部变量indexbyte中。 | 76 | | 0x3b | istore_0 | | 将栈顶int类型值保存到局部变量0中。 | 77 | | 0x3c | istore_1 | | 将栈顶int类型值保存到局部变量1中。 | 78 | | 0x3d | istore_2 | | 将栈顶int类型值保存到局部变量2中。 | 79 | | 0x3e | istore_3 | | 将栈顶int类型值保存到局部变量3中。 | 80 | | 0x37 | (wide)lstore | indexbyte | 将栈顶long类型值保存到局部变量indexbyte中。 | 81 | | 0x3f | lstore_0 | | 将栈顶long类型值保存到局部变量0中。 | 82 | | 0x40 | lstore_1 | | 将栈顶long类型值保存到局部变量1中。 | 83 | | 0x41 | lstore_2 | | 将栈顶long类型值保存到局部变量2中。 | 84 | | 0x42 | lstroe_3 | | 将栈顶long类型值保存到局部变量3中。 | 85 | | 0x38 | (wide)fstore | indexbyte | 将栈顶float类型值保存到局部变量indexbyte中。 | 86 | | 0x43 | fstore_0 | | 将栈顶float类型值保存到局部变量0中。 | 87 | | 0x44 | fstore_1 | | 将栈顶float类型值保存到局部变量1中。 | 88 | | 0x45 | fstore_2 | | 将栈顶float类型值保存到局部变量2中。 | 89 | | 0x46 | fstore_3 | | 将栈顶float类型值保存到局部变量3中。 | 90 | | 0x39 | (wide)dstore | indexbyte | 将栈顶double类型值保存到局部变量indexbyte中。 | 91 | | 0x47 | dstore_0 | | 将栈顶double类型值保存到局部变量0中。 | 92 | | 0x48 | dstore_1 | | 将栈顶double类型值保存到局部变量1中。 | 93 | | 0x49 | dstore_2 | | 将栈顶double类型值保存到局部变量2中。 | 94 | | 0x4a | dstore_3 | | 将栈顶double类型值保存到局部变量3中。 | 95 | | 0x53 | aastore | | 将栈顶引用类型值保存到指定引用类型数组的指定项。 | 96 | | 0x4f | iastore | | 将栈顶int类型值保存到指定int类型数组的指定项。 | 97 | | 0x50 | lastore | | 将栈顶long类型值保存到指定long类型数组的指定项。 | 98 | | 0x51 | fastore | | 将栈顶float类型值保存到指定float类型数组的指定项。 | 99 | | 0x52 | dastore | | 将栈顶double类型值保存到指定double类型数组的指定项。 | 100 | | 0x54 | bastroe | | 将栈顶boolean类型值或byte类型值保存到指定boolean类型数组或byte类型数组的指定项。 | 101 | | 0x55 | castore | | 将栈顶char类型值保存到指定char类型数组的指定项。 | 102 | | 0x56 | sastore | | 将栈顶short类型值保存到指定short类型数组的指定项。 | 103 | 104 | 105 | >wide指令 106 | 107 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 108 | | :----------|:-------------| :-----| :-----| 109 | | 0xc4| wide| | 使用附加字节扩展局部变量索引(iinc指令特殊)。| 110 | 111 | 112 | >通用(无类型)栈操作指令 113 | 114 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 115 | | :----------|:-------------| :-----| :-----| 116 | | 0x00 | nop | | 空操作。 | 117 | | 0x57 | pop | | 从栈顶弹出一个字长的数据。 | 118 | | 0x58 | pop2 | | 从栈顶弹出两个字长的数据。 | 119 | | 0x59 | dup | | 复制栈顶一个字长的数据,将复制后的数据压栈。 | 120 | | 0x5a | dup_x1 | | 复制栈顶一个字长的数据,弹出栈顶两个字长数据,先将复制后的数据压栈,再将弹出的两个字长数据压栈。 | 121 | | 0x5b | dup_x2 | | 复制栈顶一个字长的数据,弹出栈顶三个字长的数据,将复制后的数据压栈,再将弹出的三个字长的数据压栈。 | 122 | | 0x5c | dup2 | | 复制栈顶两个字长的数据,将复制后的两个字长的数据压栈。 | 123 | | 0x5d | dup2_x1 | | 复制栈顶两个字长的数据,弹出栈顶三个字长的数据,将复制后的两个字长的数据压栈,再将弹出的三个字长的数据压栈。 | 124 | | 0x5e | dup2_x2 | | 复制栈顶两个字长的数据,弹出栈顶四个字长的数据,将复制后的两个字长的数据压栈,再将弹出的四个字长的数据压栈。 | 125 | | 0x5f | swap | | 交换栈顶两个字长的数据的位置。Java指令中没有提供以两个字长为单位的交换指令。 | 126 | 127 | 128 | >类型转换指令 129 | 130 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 131 | | :----------|:-------------| :-----| :-----| 132 | | 0x86 | i2f | | 将栈顶int类型值转换为float类型值。 | 133 | | 0x85 | i2l | | 将栈顶int类型值转换为long类型值。 | 134 | | 0x87 | i2d | | 将栈顶int类型值转换为double类型值。 | 135 | | 0x8b | f2i | | 将栈顶float类型值转换为int类型值。 | 136 | | 0x8c | f2l | | 将栈顶float类型值转换为long类型值。 | 137 | | 0x8d | f2d | | 将栈顶float类型值转换为double类型值。 | 138 | | 0x88 | l2i | | 将栈顶long类型值转换为int类型值。 | 139 | | 0x89 | l2f | | 将栈顶long类型值转换为float类型值。 | 140 | | 0x8a | l2d | | 将栈顶long类型值转换double类型值。 | 141 | | 0x8e | d2i | | 将栈顶double类型值转换为int类型值。 | 142 | | 0x90 | d2f | | 将栈顶double类型值转换为float类型值。 | 143 | | 0x8f | d2l | | 将栈顶double类型值转换为long类型值。 | 144 | | 0x91 | i2b | | 将栈顶int类型值截断成byte类型,后带符号扩展成int类型值入栈。 | 145 | | 0x92 | i2c | | 将栈顶int类型值截断成char类型值,后带符号扩展成int类型值入栈。 | 146 | | 0x93 | i2s | | 将栈顶int类型值截断成short类型值,后带符号扩展成int类型值入栈。 | 147 | 148 | 149 | >整数运算 150 | 151 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 152 | | :----------|:-------------| :-----| :-----| 153 | | 0x60 | iadd | | 将栈顶两int类型数相加,结果入栈。 | 154 | | 0x64 | isub | | 将栈顶两int类型数相减,结果入栈。 | 155 | | 0x68 | imul | | 将栈顶两int类型数相乘,结果入栈。 | 156 | | 0x6c | idiv | | 将栈顶两int类型数相除,结果入栈。 | 157 | | 0x70 | irem | | 将栈顶两int类型数取模,结果入栈。 | 158 | | 0x74 | ineg | | 将栈顶int类型值取负,结果入栈。 | 159 | | 0x61 | ladd | | 将栈顶两long类型数相加,结果入栈。 | 160 | | 0x65 | lsub | | 将栈顶两long类型数相减,结果入栈。 | 161 | | 0x69 | lmul | | 将栈顶两long类型数相乘,结果入栈。 | 162 | | 0x6d | ldiv | | 将栈顶两long类型数相除,结果入栈。 | 163 | | 0x71 | lrem | | 将栈顶两long类型数取模,结果入栈。 | 164 | | 0x75 | lneg | | 将栈顶long类型值取负,结果入栈。 | 165 | | 0x84 | (wide)iinc | indexbyte constbyte | 将整数值constbyte加到indexbyte指定的int类型的局部变量中。 | 166 | 167 | 168 | >浮点运算 169 | 170 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 171 | | :----------|:-------------| :-----| :-----| 172 | | 0x62 | fadd | | 将栈顶两float类型数相加,结果入栈。 | 173 | | 0x66 | fsub | | 将栈顶两float类型数相减,结果入栈。 | 174 | | 0x6a | fmul | | 将栈顶两float类型数相乘,结果入栈。 | 175 | | 0x6e | fdiv | | 将栈顶两float类型数相除,结果入栈。 | 176 | | 0x72 | frem | | 将栈顶两float类型数取模,结果入栈。 | 177 | | 0x76 | fneg | | 将栈顶float类型值取反,结果入栈。 | 178 | | 0x63 | dadd | | 将栈顶两double类型数相加,结果入栈。 | 179 | | 0x67 | dsub | | 将栈顶两double类型数相减,结果入栈。 | 180 | | 0x6b | dmul | | 将栈顶两double类型数相乘,结果入栈。 | 181 | | 0x6f | ddiv | | 将栈顶两double类型数相除,结果入栈。 | 182 | | 0x73 | drem | | 将栈顶两double类型数取模,结果入栈。 | 183 | | 0x77 | dneg | | 将栈顶double类型值取负,结果入栈。 | 184 | 185 | 186 | >逻辑运算——移位运算 187 | 188 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 189 | | :----------|:-------------| :-----| :-----| 190 | | 0x78 | ishl | | 左移int类型值。 | 191 | | 0x79 | lshl | | 左移long类型值。 | 192 | | 0x7a | ishr | | 算术右移int类型值。 | 193 | | 0x7b | lshr | | 算术右移long类型值。 | 194 | | 0x7c | iushr | | 逻辑右移int类型值。 | 195 | | 0x7d | lushr | | 逻辑右移long类型值。 | 196 | 197 | 198 | >逻辑运算——按位布尔运算 199 | 200 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 201 | | :----------|:-------------| :-----| :-----| 202 | | 0x73 | iand | | 对int类型按位与运算。 | 203 | | 0x7f | land | | 对long类型的按位与运算。 | 204 | | 0x80 | ior | | 对int类型的按位或运算。 | 205 | | 0x81 | lor | | 对long类型的按位或运算。 | 206 | | 0x82 | ixor | | 对int类型的按位异或运算。 | 207 | | 0x83 | lxor | | 对long类型的按位异或运算。 | 208 | 209 | 210 | >控制流指令——条件跳转指令 211 | 212 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 213 | | :----------|:-------------| :-----| :-----| 214 | | 0x99 | ifeq | | 若栈顶int类型值为0则跳转。 | 215 | | 0x9a | ifne | | 若栈顶int类型值不为0则跳转。 | 216 | | 0x9b | iflt | | 若栈顶int类型值小于0则跳转。 | 217 | | 0x9e | ifle | | 若栈顶int类型值小于等于0则跳转。 | 218 | | 0x9d | ifgt | | 若栈顶int类型值大于0则跳转。 | 219 | | 0x9c | ifge | | 若栈顶int类型值大于等于0则跳转。 | 220 | | 0x9f | if_icmpeq | | 若栈顶两int类型值相等则跳转。 | 221 | | 0xa0 | if_icmpne | | 若栈顶两int类型值不相等则跳转。 | 222 | | 0xa1 | if_icmplt | | 若栈顶两int类型值前小于后则跳转。 | 223 | | 0xa4 | if_icmple | | 若栈顶两int类型值前小于等于后则跳转。 | 224 | | 0xa3 | if_icmpgt | | 若栈顶两int类型值前大于后则跳转。 | 225 | | 0xa2 | if_icmpge | | 若栈顶两int类型值前大于等于后则跳转。 | 226 | | 0xc6 | ifnull | | 若栈顶引用值为null则跳转。 | 227 | | 0xc7 | ifnonnull | | 若栈顶引用值不为null则跳转。 | 228 | | 0xa5 | if_acmpeq | | 若栈顶两引用类型值相等则跳转。 | 229 | | 0xa6 | if_acmpne | | 若栈顶两引用类型值不相等则跳转。 | 230 | 231 | 232 | >控制流指令——比较指令 233 | 234 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 235 | | :----------|:-------------| :-----| :-----| 236 | | 0x94 | lcmp | | 比较栈顶两long类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈。 | 237 | | 0x95 | fcmpl | | 比较栈顶两float类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。 | 238 | | 0x96 | fcmpg | | 比较栈顶两float类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。 | 239 | | 0x97 | dcmpl | | 比较栈顶两double类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。 | 240 | | 0x98 | dcmpg | | 比较栈顶两double类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。 | 241 | 242 | 243 | >控制流指令——无条件跳转指令 244 | 245 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 246 | | :----------|:-------------| :-----| :-----| 247 | |0xa7 |goto |branchbyte1 branchbyte2|无条件跳转到指定位置。| 248 | |0xc8 |goto_w|branchbyte1 branchbyte2 branchbyte3 branchbyte4|无条件跳转到指定位置(宽索引)。| 249 | 250 | 251 | >控制流指令——表跳转指令 252 | 253 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 254 | | :----------|:-------------| :-----| :-----| 255 | |0xaa |tableswitch||通过索引访问跳转表,并跳转。| 256 | |0xab|lookupswitch||通过键值访问跳转表,并跳转。| 257 | 258 | 259 | >控制流指令——异常和finally 260 | 261 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 262 | | :----------|:-------------| :-----| :-----| 263 | | 0xbf | athrow | | 抛出异常。 | 264 | | 0xa8 | jsr | | 跳转到子例程序。 | 265 | | 0xc9 | jsr_w | | 跳转到子例程序(宽索引)。 | 266 | | 0xa9 | (wide)ret | | 返回子例程序。 | 267 | 268 | 269 | >对象操作指令 270 | 271 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 272 | | :----------|:-------------| :-----| :-----| 273 | | 0xbb | new | |创建新的对象实例。 | 274 | | 0xc0 | checkcast| | 类型强转。 | 275 | | 0xc1 | instanceof | 判断类型。 | 276 | | 0xb4 | getfield || 获取对象字段的值。 | 277 | | 0xb5 | putfield || 给对象字段赋值。 | 278 | | 0xb2 | getstatic || 获取静态字段的值。 | 279 | | 0xb3 | putstatic || 给静态字段赋值。 | 280 | 281 | 282 | >数组操作指令 283 | 284 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 285 | | :----------|:-------------| :-----| :-----| 286 | | 0xbc | newarray | | 创建type类型的数组。 | 287 | | 0xbd | anewarray | | 创建引用类型的数组。 | 288 | | 0xbe | arraylength | | 获取一维数组的长度。 | 289 | | 0xc5 | multianewarray | | 创建dimension维度的数组。 | 290 | 291 | 292 | >方法调用指令 293 | 294 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 295 | | :----------|:-------------| :-----| :-----| 296 | | 0xb7 | invokespecial | | 编译时方法绑定调用方法。 | 297 | | 0xb6 | invokevirtual | | 运行时方法绑定调用方法。 | 298 | | 0xb8 | invokestatic | | 调用静态方法。 | 299 | | 0xb9 | invokeinterface | | 调用接口方法。 | 300 | 301 | 302 | >方法返回指令 303 | 304 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 305 | | :----------|:-------------| :-----| :-----| 306 | | 0xac | ireturn | | 返回int类型值。 | 307 | | 0xad | lreturn | | 返回long类型值。 | 308 | | 0xae | freturn | | 返回float类型值。 | 309 | | 0xaf | dreturn | | 返回double类型值。 | 310 | | 0xb0 | areturn | | 返回引用类型值。 | 311 | | 0xb1 | return | | void函数返回。 | 312 | 313 | 314 | >线程同步指令 315 | 316 | | 指令码 | 助记符 | 操作数 |描述(栈指操作数栈) | 317 | | :----------|:-------------| :-----| :-----| 318 | |0xc2| monitorenter|| 进入并获得对象监视器。| 319 | |0xc3| monitorexit|| 释放并退出对象监视器。| 320 | 321 | ------------ 322 | 323 | ![微信公众号:bugstack虫洞栈,欢迎您的关注&获取源码!](https://upload-images.jianshu.io/upload_images/17387004-6eef8bf4dd640ab5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 324 | 325 | --------------------------------------------------------------------------------