├── .gitattributes ├── .github └── workflows │ └── continuous_integration.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assembler.iml ├── bin ├── assembler.bat └── assembler.sh ├── build.gradle ├── docs ├── md │ ├── building.md │ ├── downloads.md │ ├── img │ │ ├── assembler.png │ │ └── guardsquare.png │ ├── index.md │ ├── license.md │ ├── releasenotes.md │ └── specification.md └── mkdocs.yml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pga-cli ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── guardsquare │ └── proguard │ └── assembler │ └── AssemblerCli.java ├── pga-lib ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── guardsquare │ │ └── proguard │ │ ├── assembler │ │ ├── AnnotationsParser.java │ │ ├── AssemblyConstants.java │ │ ├── AttributesParser.java │ │ ├── ClassMembersParser.java │ │ ├── ClassParser.java │ │ ├── ConstantParser.java │ │ ├── InstructionsParser.java │ │ ├── ParseException.java │ │ ├── Parser.java │ │ └── io │ │ │ └── JbcReader.java │ │ └── disassembler │ │ ├── AnnotationsPrinter.java │ │ ├── AttributesPrinter.java │ │ ├── ClassMembersPrinter.java │ │ ├── ClassPrinter.java │ │ ├── ConstantPrinter.java │ │ ├── InstructionsPrinter.java │ │ ├── LabelsCollector.java │ │ ├── PrintException.java │ │ ├── Printer.java │ │ └── io │ │ └── JbcDataEntryWriter.java │ └── test │ └── kotlin │ └── com │ └── guardsquare │ └── proguard │ └── AssemblerErrorsTest.kt └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | gradlew -text 2 | gradlew.bat -text 3 | -------------------------------------------------------------------------------- /.github/workflows/continuous_integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | push: 7 | branches: 8 | - master 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | path: proguard-assembler 16 | - uses: actions/setup-java@v1 17 | with: 18 | java-version: 8 19 | - uses: eskatos/gradle-command-action@v1 20 | with: 21 | build-root-directory: proguard-assembler/ 22 | wrapper-directory: proguard-assembler/ 23 | arguments: build 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .gradle/ 3 | /lib/ 4 | *.iml 5 | .idea 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the ProGuard Assembler and Disassembler 2 | 3 | Thank you for contributing! 4 | 5 | - You can report issues on our [issue tracker](issues) on GitHub. Please be 6 | concise and complete. If we can't reproduce an issue, we most likely can't 7 | fix it. 8 | 9 | - You can also clone the [source code](.) yourself and create [pull 10 | requests](pulls) on GitHub. 11 | 12 | These are a few guidelines for our code and documentation. 13 | 14 | ## Basic principle 15 | 16 | Above all, code should be consistent. Study, respect, and follow the style of 17 | the existing code. 18 | 19 | ## Code design 20 | 21 | The code generally defines many short classes, as the building blocks for 22 | classes that contain the real logic. This is sometimes taken to the extreme: 23 | even loops and conditional statements can often be implemented as separate 24 | classes. Even though these classes are verbose and repetitive, the resulting 25 | main code becomes much more compact, flexible, and robust. 26 | 27 | ### Data classes 28 | 29 | Basic data classes define the data structures, with just a minimum number of 30 | operations: Java bytecode classes, Dex bytecode classes, XML data, native 31 | libraries, etc. They typically reflect their respective specifications 32 | literally. The data classes have one or more `accept` methods to let the 33 | visitor classes below operate on them. 34 | 35 | ### Visitors 36 | 37 | The code relies a lot on the visitor pattern. Visitor classes define the 38 | operations on the data: reading, writing, editing, transforming, etc. The 39 | visitor classes have one or more `visit` methods to operate on data classes of 40 | the same basic type. 41 | 42 | For example, a Java bytecode class contains a constant pool with constants of 43 | different types: integer constants, float constants, string constants, etc. 44 | The data classes IntegerConstant, FloatConstant, StringConstant, etc. all 45 | implement the basic type Constant. The visitor interface ConstantVisitor 46 | contains methods 'visitIntegerConstant', 'visitFloatConstant', 47 | 'visitStringConstant', etc. Implementations of this visitor interface can 48 | perform all kinds of operations on the constants. 49 | 50 | The reasoning behind this pattern is that the data classes are very stable, 51 | because they are directly based on the specifications (class files, dex files, 52 | binary XML files, ELF files, etc). The operations are more dynamic. They are 53 | changed and extended all the time. It is practically impossible to add all 54 | possible operations in the data classes, but it is easy to add another 55 | implementation of a visitor interface. Implementing an interface in practice 56 | helps a lot to think of all possible cases. 57 | 58 | The visitor pattern uses visitor interfaces to operate on the similar elements 59 | of a data structure. Each interface often has many implementations. A great 60 | disadvantage at this time is that visitor methods can invoke one another 61 | (directly or indirectly), but they can't communicate easily. Since the 62 | implementations can't add their own parameters or return values, they often 63 | have to rely on fields to pass values back and forth. This is more 64 | error-prone. Still, the advantages of the visitor pattern outweigh the 65 | disadvantages. 66 | 67 | We prefer (visitor) interfaces with many classes that implement them, so the 68 | implementations can be used as building blocks. 69 | 70 | ### Dependency injection 71 | 72 | We heavily use **constructor-based dependency injection**, to create immutable 73 | objects. Notably the visitors are often like commands that are combined in an 74 | immutable structure, via constructors. The commands are then executed by 75 | applying the visitors to the classes or other data structures. 76 | 77 | ### Getters and setters 78 | 79 | We try to **avoid getters and setters** outside of pure data classes. 80 | Generally, getters and setters break data encapsulation -- "Tell, don't ask". 81 | In our architecture, visitors often delegate to a chain of other visitors, 82 | with the final visitor applying actual changes to the data structures. 83 | Visitors that change the data classes often access fields directly, since they 84 | conceptually have the same purpose as methods inside the data classes. 85 | 86 | ## Code style 87 | 88 | Over time, we've adopted a few guidelines: 89 | 90 | - Historically, ProGuard hasn't used generics, enums, iterable loops, variable 91 | arguments, or closures. More recent code can use the features of Java 8, but 92 | be consistent. 93 | 94 | - Prefer **arrays** over lists, if their sizes are relatively static. The 95 | resulting code is easier to read, without casts or generics. 96 | 97 | - Prefer **short classes** over long classes, although long classes that simply 98 | implement the many methods of their interfaces are okay. 99 | 100 | - Prefer **short methods**, although long methods with a linear flow are okay. 101 | 102 | - **Declare and initialize** fields and variables at the same time, when 103 | possible. 104 | 105 | - Make fields **final**, when possible: 106 | ``` 107 | private final CodeAttributeComposer composer = new CodeAttributeComposer(); 108 | ``` 109 | 110 | - We generally don't make local variables final, to avoid cluttering the code 111 | too much. 112 | 113 | - Maintain a **consistent order** for fields, methods (e.g.. getters and 114 | setters), case statements, etc. A good basic rule for new classes is that 115 | their contents should be in chronological order or respecting the natural 116 | hierarchical order of the data -- Which fields are accessed first? Which 117 | methods are accessed first? Which data comes first in the data structures? 118 | For classes that implement or resemble other classes, the contents should 119 | respect the same order. 120 | 121 | ## Documentation 122 | 123 | All classes must have main javadoc: 124 | 125 | /** 126 | * This {@link ClassVisitor} collects the names of the classes that it 127 | * visits in the given collection. 128 | * 129 | * @author Eric Lafortune 130 | */ 131 | 132 | Methods should have javadoc: 133 | 134 | /** 135 | * Returns the number of parameters of the given internal method descriptor. 136 | * @param internalMethodDescriptor the internal method descriptor, 137 | * e.g. "(ID)Z". 138 | * @param isStatic specifies whether the method is static, 139 | * e.g. false. 140 | * @return the number of parameters, 141 | * e.g. 3. 142 | */ 143 | 144 | In practice, fields rarely have javadoc (this is debatable...) 145 | 146 | Methods that implement interfaces generally don't have javadoc, since they 147 | simply implement the behavior specified by the interfaces. Instead, a group of 148 | methods that implements an interface is documented with: 149 | 150 | // Implementations for SomeInterface. 151 | 152 | Most code has comments for each block of statements. The idea is to make the 153 | code readable by just glancing over the comments (which works well if they are 154 | colored in an IDE). 155 | 156 | Comments are generally short sentences, starting with a capital and ending 157 | with a period: 158 | 159 | // This is a comment. 160 | 161 | For the manual (written as markdown), you should follow the excellent advice 162 | of the [Google Developer Documentation Style 163 | Guide](https://developers.google.com/style/). 164 | 165 | ## Code format 166 | 167 | We strive for code that is super-aligned. Ideally, the code should look like a 168 | set of tables, for variable declarations, parameter declarations, ternary 169 | operators, etc. 170 | 171 | - The basic indentation is 4 spaces (never tabs). 172 | 173 | - Curly braces are on separate lines. 174 | 175 | - The ideal maximum line length is 120 characters. Documentation and comments 176 | should stick to this limit. Code that has a nice table-like structure can 177 | exceed it. 178 | 179 | - Imports of multiple classes in the same package are specified with a 180 | wildcard '*'. 181 | 182 | - Try to preserve a logical, consistent order in fields, getters/setters, 183 | methods, variables. 184 | 185 | - Whenever overriding a method, add the @Override annotation. 186 | 187 | In practice, an IDE can help to obtain a clean indentation and structure, but 188 | it is often not sufficient. Be careful to never upset a cleanly formatted 189 | source file by automatically reformatting it. 190 | 191 | ### Naming Conventions 192 | 193 | - Names (class names, field names, etc) should be descriptive. 194 | 195 | - We generally don't abbreviate names, not even index variables: 196 | ``` 197 | for (int index = 0; index < count; index++) 198 | ``` 199 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Assembler and Disassembler for Java class files 2 | 3 | The **ProGuard Assembler and Disassembler** can assemble and disassemble 4 | Java class files. 5 | 6 | The disassembler takes `class` files and converts them to readable `jbc` 7 | (Java ByteCode) files, following the [ProGuard Assembly Language 8 | specification](docs/md/specification.md). 9 | 10 | The assembler does the opposite; it takes readable `jbc` files and 11 | converts them to `class` files. 12 | 13 | ## Usage 14 | 15 | The program is distributed as a single executable jar file that serves both as 16 | an assembler and as a disassembler. When the program detects a `jbc` file as 17 | input, it assembles it. Vice versa, when it detects a `class` file, it 18 | disassembles it. It can handle any combination of both; a jar file containing 19 | both `jbc` files and `class` files will have its `jbc` files assembled in the 20 | output and its `class` files disassembled in the output. 21 | 22 | bin/assembler [] 23 | 24 | The _input_ and the _output_ can be .class/.jbc/.jar/.jmod files or 25 | directories, where _.jbc files_ contain disassembled Java bytecode. 26 | 27 | The _classpath_ (with runtime classes and library classes) is only necessary 28 | for preverifying assembled code. 29 | 30 | ## Example 31 | 32 | As a small test, you can let the program disassemble itself: 33 | 34 | bin/assembler lib/assembler.jar disassembled 35 | 36 | The program will create a `disassembled` directory with readable files 37 | disassembled from the input jar. You can then let the program assemble the 38 | assembly files again: 39 | 40 | bin/assembler /usr/lib/jvm/java-12-oracle/jmods/java.base.jmod disassembled assembled.jar 41 | 42 | The program will now create an `assembled.jar` file with reassembled class 43 | files. It will use the `jmod` file as a run-time library to properly preverify 44 | the code. 45 | 46 | ## Preverification 47 | 48 | The ProGuard Assembly Language contains some syntactic sugar, notably for the 49 | `StackMap` and `StackMapTable` attributes. As of Java version 7, each `Code` 50 | attribute must contain a `StackMapTable` attribute. Since it would be very 51 | cumbersome to write these attributes by hand, the assembler automatically 52 | preverifies every method using the ProGuard preverifier, and creates the 53 | `StackMapTable` using the preverification info. As a consequence, the 54 | assembler needs to know the location of any library jars when assembling 55 | `class` files. 56 | 57 | ## Downloads 58 | 59 | The code is written in Java, so it requires a Java Runtime Environment 60 | (JRE 1.8 or higher). 61 | 62 | You can download the assembler and disassembler in various forms: 63 | 64 | - [Pre-built artifacts](https://search.maven.org/search?q=g:com.guardsquare) at Maven Central 65 | - A [Git repository of the source code](https://github.com/Guardsquare/proguard-assembler) at Github 66 | 67 | ## Building 68 | 69 | If you've downloaded the source code, you can build it with Gradle: 70 | 71 | ./gradlew clean build 72 | 73 | Once built, you can [run the assembler and disassembler](index.md) with the 74 | script `bin/assembler.sh` or `bin/assembler.bat`. 75 | 76 | ## Using as a library 77 | 78 | You can use the ProGuard assembler or disassembler from your own code by 79 | adding a dependency on the ProGuard assembler library. For a more complete 80 | example, see the CLI project (`pga-cli`). 81 | 82 | ```kotlin 83 | import com.guardsquare.proguard.assembler.io.JbcReader 84 | import proguard.classfile.ClassPool 85 | import proguard.classfile.VersionConstants.CLASS_VERSION_1_6 86 | import proguard.classfile.attribute.visitor.AllAttributeVisitor 87 | import proguard.classfile.util.ClassReferenceInitializer 88 | import proguard.classfile.util.ClassSuperHierarchyInitializer 89 | import proguard.classfile.visitor.AllMethodVisitor 90 | import proguard.classfile.visitor.ClassPoolFiller 91 | import proguard.classfile.visitor.ClassVersionFilter 92 | import proguard.io.ClassDataEntryWriter 93 | import proguard.io.DataEntryNameFilter 94 | import proguard.io.DataEntryReader 95 | import proguard.io.DataEntryWriter 96 | import proguard.io.FileSource 97 | import proguard.io.FilteredDataEntryReader 98 | import proguard.io.FixedFileWriter 99 | import proguard.io.IdleRewriter 100 | import proguard.io.RenamedDataEntryWriter 101 | import proguard.io.util.IOUtil 102 | import proguard.preverify.CodePreverifier 103 | import proguard.util.ConcatenatingStringFunction 104 | import proguard.util.ConstantStringFunction 105 | import proguard.util.ExtensionMatcher 106 | import proguard.util.SuffixRemovingStringFunction 107 | import java.io.File 108 | 109 | 110 | fun main(args: Array) { 111 | // Read a JBC file and write a class file 112 | 113 | val inputSource = FileSource(File(args[0])) 114 | 115 | val programClassPool = ClassPool() 116 | val libraryClassPool = if (args.size > 2) IOUtil.read(args[2], true) else ClassPool() 117 | 118 | val jbcReader: DataEntryReader = JbcReader( 119 | ClassPoolFiller(programClassPool) 120 | ) 121 | 122 | val reader: DataEntryReader = FilteredDataEntryReader( 123 | DataEntryNameFilter(ExtensionMatcher(".jbc")), jbcReader 124 | ) 125 | 126 | inputSource.pumpDataEntries(reader) 127 | 128 | // Preverify before writing the class 129 | preverify(programClassPool, libraryClassPool) 130 | 131 | val outputFile = File(args[1]) 132 | val writer = FixedFileWriter(outputFile) 133 | 134 | val jbcAsClassWriter: DataEntryWriter = RenamedDataEntryWriter( 135 | ConcatenatingStringFunction( 136 | SuffixRemovingStringFunction(".jbc"), 137 | ConstantStringFunction(".class") 138 | ), 139 | ClassDataEntryWriter(programClassPool, writer) 140 | ) 141 | 142 | // Write the class file by reading in the jbc file and 143 | // replacing it using the `IdleRewriter`. 144 | inputSource.pumpDataEntries( 145 | FilteredDataEntryReader( 146 | DataEntryNameFilter(ExtensionMatcher(".jbc")), 147 | IdleRewriter(jbcAsClassWriter) 148 | ) 149 | ) 150 | 151 | writer.close() 152 | } 153 | 154 | fun preverify(libraryClassPool: ClassPool, programClassPool: ClassPool) { 155 | programClassPool.classesAccept(ClassReferenceInitializer(programClassPool, libraryClassPool)) 156 | programClassPool.classesAccept(ClassSuperHierarchyInitializer(programClassPool, libraryClassPool)) 157 | libraryClassPool.classesAccept(ClassReferenceInitializer(programClassPool, libraryClassPool)) 158 | libraryClassPool.classesAccept(ClassSuperHierarchyInitializer(programClassPool, libraryClassPool)) 159 | programClassPool.classesAccept( 160 | ClassVersionFilter( 161 | CLASS_VERSION_1_6, 162 | AllMethodVisitor( 163 | AllAttributeVisitor( 164 | CodePreverifier(false) 165 | ) 166 | ) 167 | ) 168 | ) 169 | } 170 | ``` 171 | 172 | 173 | ## Contributing 174 | 175 | The **ProGuard Assembler and Disassembler** are build on the 176 | [ProGuardCORE](https://github.com/Guardsquare/proguard-core) library. 177 | 178 | Contributions, issues and feature requests are welcome in both projects. 179 | Feel free to check the [issues](issues) page and the [contributing 180 | guide](CONTRIBUTING.md) if you would like to contribute. 181 | 182 | ## License 183 | 184 | The **ProGuard Assembler and Disassembler** are distributed under the terms of 185 | the [Apache License Version 2.0](LICENSE). 186 | 187 | Enjoy! 188 | 189 | Copyright (c) 2002-2022 [Guardsquare NV](https://www.guardsquare.com/) 190 | -------------------------------------------------------------------------------- /assembler.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /bin/assembler.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Start-up script for the ProGuard Assembler and Disassembler. 4 | REM 5 | REM Note: when passing file names containing spaces to this script, 6 | REM you'll have to add escaped quotes around them, e.g. 7 | REM "\"C:/My Directory/My File.txt\"" 8 | 9 | IF EXIST "%PROGUARD_HOME%" GOTO home 10 | SET PROGUARD_HOME=%~dp0\.. 11 | :home 12 | 13 | java -jar "%PROGUARD_HOME%\lib\assembler.jar" %* 14 | -------------------------------------------------------------------------------- /bin/assembler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Start-up script for the ProGuard Assembler and Disassembler. 4 | # 5 | # Note: when passing file names containing spaces to this script, 6 | # you'll have to add escaped quotes around them, e.g. 7 | # "\"/My Directory/My File.txt\"" 8 | 9 | # Account for possibly missing/basic readlink. 10 | # POSIX conformant (dash/ksh/zsh/bash). 11 | ASSEMBLER=`readlink -f "$0" 2>/dev/null` 12 | if test "$ASSEMBLER" = '' 13 | then 14 | ASSEMBLER=`readlink "$0" 2>/dev/null` 15 | if test "$ASSEMBLER" = '' 16 | then 17 | ASSEMBLER="$0" 18 | fi 19 | fi 20 | 21 | ASSEMBLER_HOME=`dirname "$ASSEMBLER"`/.. 22 | 23 | # Find the compiled jar. 24 | if [ -f "$ASSEMBLER_HOME/lib/assembler.jar" ] 25 | then 26 | ASSEMBLER_JAR=$ASSEMBLER_HOME/lib/assembler.jar 27 | else 28 | echo 'Please build the project first' 29 | exit 1 30 | fi 31 | 32 | exec java -jar "$ASSEMBLER_JAR" "$@" 33 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import io.github.gradlenexus.publishplugin.NexusRepository 2 | 3 | plugins { 4 | id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' 5 | id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false 6 | id 'com.adarshr.test-logger' version '3.2.0' apply false 7 | id "org.jlleitschuh.gradle.ktlint" version "10.3.0" apply false 8 | id "java-test-fixtures" 9 | } 10 | 11 | allprojects { 12 | group = 'com.guardsquare' 13 | version = '1.0.0' 14 | } 15 | 16 | subprojects { 17 | repositories { 18 | mavenCentral() 19 | mavenLocal() 20 | } 21 | } 22 | 23 | nexusPublishing { 24 | repositories { 25 | sonatype { 26 | username = findProperty('PROGUARD_STAGING_USERNAME') as NexusRepository 27 | password = findProperty('PROGUARD_STAGING_PASSWORD') as NexusRepository 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/md/building.md: -------------------------------------------------------------------------------- 1 | If you've downloaded the source code of the the **ProGuard Assembler and 2 | Disassembler**, you can build it yourself with Gradle: 3 | 4 | - Build the artifacts: 5 | 6 | ./gradlew assemble 7 | 8 | - Build the artifacts when you also have a personal copy of the [ProGuard 9 | Core](https://github.com/Guardsquare/proguard-core) library: 10 | 11 | ./gradlew --include-build assemble 12 | 13 | - Publish the artifacts to your local Maven cache (something like `~/.m2/`): 14 | 15 | ./gradlew publishToMavenLocal 16 | 17 | - Build tar and zip archives with the binaries and documentation: 18 | 19 | ./gradlew distTar distZip 20 | 21 | Once built, you can [run the assembler and disassembler](index.md) with the 22 | script `bin/assembler.sh` or `bin/assembler.bat`. 23 | -------------------------------------------------------------------------------- /docs/md/downloads.md: -------------------------------------------------------------------------------- 1 | The **ProGuard Assembler and Disassembler** are distributed under the terms of 2 | the Apache License Version 2.0. Please consult the [license page](license.md) 3 | for more details. 4 | 5 | The code is written in Java, so it requires a Java Runtime Environment 6 | (JRE 1.8 or higher). 7 | 8 | You can download the assembler and disassembler in various forms: 9 | 10 | - [Pre-built artifacts](https://bintray.com/guardsquare/proguard) at JCenter 11 | - [Pre-built artifacts](https://search.maven.org/search?q=g:net.sf.proguard) at Maven Central 12 | - A [Git repository of the source code](https://github.com/Guardsquare/proguard-assembler) at Github 13 | 14 | You can find major releases, minor releases with important bug fixes, and 15 | beta releases with the latest new features and any less urgent bug fixes. 16 | 17 | If you're still working with an older version of the software, check out the 18 | [release notes](releasenotes.md), to see if you're missing something essential. 19 | -------------------------------------------------------------------------------- /docs/md/img/assembler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guardsquare/proguard-assembler/a923540f0a0b2cae6acb52c3a4e50eff45129abf/docs/md/img/assembler.png -------------------------------------------------------------------------------- /docs/md/img/guardsquare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guardsquare/proguard-assembler/a923540f0a0b2cae6acb52c3a4e50eff45129abf/docs/md/img/guardsquare.png -------------------------------------------------------------------------------- /docs/md/index.md: -------------------------------------------------------------------------------- 1 | The **ProGuard Assembler and Disassembler** can assemble and disassemble 2 | Java class files. 3 | 4 | The disassembler takes `class` files and converts them to readable `jbc` 5 | (Java ByteCode) files, following the [ProGuard Assembly Language 6 | specification](specification.md). 7 | 8 | The assembler does the opposite; it takes readable `jbc` files and 9 | converts them to `class` files. 10 | 11 | # Usage 12 | 13 | The program is distributed as a single executable jar file that serves both as 14 | an assembler and as a disassembler. When the program detects a `jbc` file as 15 | input, it assembles it. Vice versa, when it detects a `class` file, it 16 | disassembles it. It can handle a combination of both; a jar file containing 17 | both `jbc` files and `class` files will have its `jbc` files assembled in the 18 | output and its `class` files disassembled in the output. 19 | 20 | bin/assembler [] 21 | 22 | The _input_ and the _output_ can be .class/.jbc/.jar/.jmod files or 23 | directories, where _.jbc files_ contain disassembled Java bytecode. 24 | 25 | The _classpath_ (with runtime classes and library classes) is only necessary 26 | for preverifying assembled code. 27 | 28 | # Example 29 | 30 | As a small test, you can let the program disassemble itself: 31 | 32 | bin/assembler lib/assembler.jar disassembled 33 | 34 | The program will create a `disassembled` directory with readable files 35 | disassembled from the input jar. You can then let the program assemble the 36 | assembly files again: 37 | 38 | bin/assembler /usr/lib/jvm/java-12-oracle/jmods/java.base.jmod disassembled assembled.jar 39 | 40 | The program will now create an `assembled.jar` file with reassembled class 41 | files. It will use the `jmod` file as a run-time library to properly preverify 42 | the code. 43 | 44 | # Preverification 45 | 46 | The ProGuard Assembly Language contains some syntactic sugar, notably for the 47 | `StackMap` and `StackMapTable` attributes. As of Java version 7, each `Code` 48 | attribute must contain a `StackMapTable` attribute. Since it would be very 49 | cumbersome to write these attributes by hand, the assembler automatically 50 | preverifies every method using the ProGuard preverifier, and creates the 51 | `StackMapTable` using the preverification info. As a consequence, the 52 | assembler needs to know the location of any library jars when assembling 53 | `class` files. 54 | -------------------------------------------------------------------------------- /docs/md/license.md: -------------------------------------------------------------------------------- 1 | The ProGuard Assembler and Disassembler are licensed under the Apache License 2 | Version 2.0. 3 | 4 | Copyright 2002-2020 Guardsquare NV 5 | 6 | Apache License 7 | Version 2.0, January 2004 8 | http://www.apache.org/licenses/ 9 | 10 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 11 | 12 | 1. Definitions. 13 | 14 | "License" shall mean the terms and conditions for use, reproduction, 15 | and distribution as defined by Sections 1 through 9 of this document. 16 | 17 | "Licensor" shall mean the copyright owner or entity authorized by 18 | the copyright owner that is granting the License. 19 | 20 | "Legal Entity" shall mean the union of the acting entity and all 21 | other entities that control, are controlled by, or are under common 22 | control with that entity. For the purposes of this definition, 23 | "control" means (i) the power, direct or indirect, to cause the 24 | direction or management of such entity, whether by contract or 25 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 26 | outstanding shares, or (iii) beneficial ownership of such entity. 27 | 28 | "You" (or "Your") shall mean an individual or Legal Entity 29 | exercising permissions granted by this License. 30 | 31 | "Source" form shall mean the preferred form for making modifications, 32 | including but not limited to software source code, documentation 33 | source, and configuration files. 34 | 35 | "Object" form shall mean any form resulting from mechanical 36 | transformation or translation of a Source form, including but 37 | not limited to compiled object code, generated documentation, 38 | and conversions to other media types. 39 | 40 | "Work" shall mean the work of authorship, whether in Source or 41 | Object form, made available under the License, as indicated by a 42 | copyright notice that is included in or attached to the work 43 | (an example is provided in the Appendix below). 44 | 45 | "Derivative Works" shall mean any work, whether in Source or Object 46 | form, that is based on (or derived from) the Work and for which the 47 | editorial revisions, annotations, elaborations, or other modifications 48 | represent, as a whole, an original work of authorship. For the purposes 49 | of this License, Derivative Works shall not include works that remain 50 | separable from, or merely link (or bind by name) to the interfaces of, 51 | the Work and Derivative Works thereof. 52 | 53 | "Contribution" shall mean any work of authorship, including 54 | the original version of the Work and any modifications or additions 55 | to that Work or Derivative Works thereof, that is intentionally 56 | submitted to Licensor for inclusion in the Work by the copyright owner 57 | or by an individual or Legal Entity authorized to submit on behalf of 58 | the copyright owner. For the purposes of this definition, "submitted" 59 | means any form of electronic, verbal, or written communication sent 60 | to the Licensor or its representatives, including but not limited to 61 | communication on electronic mailing lists, source code control systems, 62 | and issue tracking systems that are managed by, or on behalf of, the 63 | Licensor for the purpose of discussing and improving the Work, but 64 | excluding communication that is conspicuously marked or otherwise 65 | designated in writing by the copyright owner as "Not a Contribution." 66 | 67 | "Contributor" shall mean Licensor and any individual or Legal Entity 68 | on behalf of whom a Contribution has been received by Licensor and 69 | subsequently incorporated within the Work. 70 | 71 | 2. Grant of Copyright License. Subject to the terms and conditions of 72 | this License, each Contributor hereby grants to You a perpetual, 73 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 74 | copyright license to reproduce, prepare Derivative Works of, 75 | publicly display, publicly perform, sublicense, and distribute the 76 | Work and such Derivative Works in Source or Object form. 77 | 78 | 3. Grant of Patent License. Subject to the terms and conditions of 79 | this License, each Contributor hereby grants to You a perpetual, 80 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 81 | (except as stated in this section) patent license to make, have made, 82 | use, offer to sell, sell, import, and otherwise transfer the Work, 83 | where such license applies only to those patent claims licensable 84 | by such Contributor that are necessarily infringed by their 85 | Contribution(s) alone or by combination of their Contribution(s) 86 | with the Work to which such Contribution(s) was submitted. If You 87 | institute patent litigation against any entity (including a 88 | cross-claim or counterclaim in a lawsuit) alleging that the Work 89 | or a Contribution incorporated within the Work constitutes direct 90 | or contributory patent infringement, then any patent licenses 91 | granted to You under this License for that Work shall terminate 92 | as of the date such litigation is filed. 93 | 94 | 4. Redistribution. You may reproduce and distribute copies of the 95 | Work or Derivative Works thereof in any medium, with or without 96 | modifications, and in Source or Object form, provided that You 97 | meet the following conditions: 98 | 99 | (a) You must give any other recipients of the Work or 100 | Derivative Works a copy of this License; and 101 | 102 | (b) You must cause any modified files to carry prominent notices 103 | stating that You changed the files; and 104 | 105 | (c) You must retain, in the Source form of any Derivative Works 106 | that You distribute, all copyright, patent, trademark, and 107 | attribution notices from the Source form of the Work, 108 | excluding those notices that do not pertain to any part of 109 | the Derivative Works; and 110 | 111 | (d) If the Work includes a "NOTICE" text file as part of its 112 | distribution, then any Derivative Works that You distribute must 113 | include a readable copy of the attribution notices contained 114 | within such NOTICE file, excluding those notices that do not 115 | pertain to any part of the Derivative Works, in at least one 116 | of the following places: within a NOTICE text file distributed 117 | as part of the Derivative Works; within the Source form or 118 | documentation, if provided along with the Derivative Works; or, 119 | within a display generated by the Derivative Works, if and 120 | wherever such third-party notices normally appear. The contents 121 | of the NOTICE file are for informational purposes only and 122 | do not modify the License. You may add Your own attribution 123 | notices within Derivative Works that You distribute, alongside 124 | or as an addendum to the NOTICE text from the Work, provided 125 | that such additional attribution notices cannot be construed 126 | as modifying the License. 127 | 128 | You may add Your own copyright statement to Your modifications and 129 | may provide additional or different license terms and conditions 130 | for use, reproduction, or distribution of Your modifications, or 131 | for any such Derivative Works as a whole, provided Your use, 132 | reproduction, and distribution of the Work otherwise complies with 133 | the conditions stated in this License. 134 | 135 | 5. Submission of Contributions. Unless You explicitly state otherwise, 136 | any Contribution intentionally submitted for inclusion in the Work 137 | by You to the Licensor shall be under the terms and conditions of 138 | this License, without any additional terms or conditions. 139 | Notwithstanding the above, nothing herein shall supersede or modify 140 | the terms of any separate license agreement you may have executed 141 | with Licensor regarding such Contributions. 142 | 143 | 6. Trademarks. This License does not grant permission to use the trade 144 | names, trademarks, service marks, or product names of the Licensor, 145 | except as required for reasonable and customary use in describing the 146 | origin of the Work and reproducing the content of the NOTICE file. 147 | 148 | 7. Disclaimer of Warranty. Unless required by applicable law or 149 | agreed to in writing, Licensor provides the Work (and each 150 | Contributor provides its Contributions) on an "AS IS" BASIS, 151 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 152 | implied, including, without limitation, any warranties or conditions 153 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 154 | PARTICULAR PURPOSE. You are solely responsible for determining the 155 | appropriateness of using or redistributing the Work and assume any 156 | risks associated with Your exercise of permissions under this License. 157 | 158 | 8. Limitation of Liability. In no event and under no legal theory, 159 | whether in tort (including negligence), contract, or otherwise, 160 | unless required by applicable law (such as deliberate and grossly 161 | negligent acts) or agreed to in writing, shall any Contributor be 162 | liable to You for damages, including any direct, indirect, special, 163 | incidental, or consequential damages of any character arising as a 164 | result of this License or out of the use or inability to use the 165 | Work (including but not limited to damages for loss of goodwill, 166 | work stoppage, computer failure or malfunction, or any and all 167 | other commercial damages or losses), even if such Contributor 168 | has been advised of the possibility of such damages. 169 | 170 | 9. Accepting Warranty or Additional Liability. While redistributing 171 | the Work or Derivative Works thereof, You may choose to offer, 172 | and charge a fee for, acceptance of support, warranty, indemnity, 173 | or other liability obligations and/or rights consistent with this 174 | License. However, in accepting such obligations, You may act only 175 | on Your own behalf and on Your sole responsibility, not on behalf 176 | of any other Contributor, and only if You agree to indemnify, 177 | defend, and hold each Contributor harmless for any liability 178 | incurred by, or claims asserted against, such Contributor by reason 179 | of your accepting any such warranty or additional liability. 180 | 181 | END OF TERMS AND CONDITIONS 182 | -------------------------------------------------------------------------------- /docs/md/releasenotes.md: -------------------------------------------------------------------------------- 1 | ## Version 1.0 (Jan 2020) 2 | 3 | | Version| Issue | Module | Explanation 4 | |--------|----------|----------|---------------------------------- 5 | | 1.0.0 | | ASSEM | Initial release. 6 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | ################################################################### 2 | # Project information 3 | ################################################################### 4 | site_name: ProGuard Assembler and Disassembler 5 | site_description: An assembler and disassembler for Java bytecode. 6 | site_author: Guardsquare NV 7 | copyright: Copyright © 2002-2020 Guardsquare NV 8 | 9 | ################################################################### 10 | # Options 11 | ################################################################### 12 | theme: 13 | name: null 14 | custom_dir: ../../proguard_lib/buildscripts/mkdocs/material 15 | logo: img/assembler.png 16 | favicon: img/guardsquare.png 17 | language: en 18 | palette: blue 19 | font: 20 | feature: 21 | 22 | docs_dir: md 23 | site_dir: html 24 | 25 | #extra_css: 26 | # - extra.css 27 | 28 | use_directory_urls: false 29 | #strict: true # broken links are errors 30 | 31 | ################################################################### 32 | # Theme specific 33 | ################################################################### 34 | extra: 35 | #font: 36 | # text: 'Droid Sans' 37 | # code: 'Ubuntu Mono' 38 | social: 39 | - type: 'twitter' 40 | link: 'https://twitter.com/guardsquare' 41 | - type: 'linkedin' 42 | link: 'https://www.linkedin.com/company/guardsquare-nv' 43 | - type: 'facebook' 44 | link: 'https://www.facebook.com/guardsquare' 45 | #feature: 46 | # tabs: true 47 | 48 | ################################################################### 49 | # Extensions 50 | ################################################################### 51 | markdown_extensions: 52 | - attr_list 53 | - admonition 54 | - footnotes 55 | - def_list 56 | 57 | ################################################################### 58 | # Page tree 59 | ################################################################### 60 | nav: 61 | - Introduction: index.md 62 | - Specification: specification.md 63 | - License: license.md 64 | - Downloads: downloads.md 65 | - Building: building.md 66 | - Release notes: releasenotes.md 67 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle build properties for the ProGuard Assembler and Disassembler. 2 | 3 | target = 1.8 4 | 5 | kotlinVersion = 1.9.0 6 | kotestVersion = 5.6.2 7 | proguardCoreVersion = 9.1.4 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guardsquare/proguard-assembler/a923540f0a0b2cae6acb52c3a4e50eff45129abf/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /pga-cli/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'distribution' 3 | id 'java' 4 | id 'application' 5 | id 'maven-publish' 6 | id 'signing' 7 | } 8 | 9 | java { 10 | toolchain { 11 | languageVersion.set(JavaLanguageVersion.of(8)) 12 | } 13 | } 14 | 15 | dependencies { 16 | implementation project(":pga-lib") 17 | } 18 | 19 | application { 20 | mainClass = 'com.guardsquare.proguard.assembler.AssemblerCli' 21 | } 22 | 23 | jar { 24 | dependsOn(":pga-lib:jar") 25 | manifest { 26 | attributes 'Main-Class': application.mainClass 27 | } 28 | from { 29 | configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } 30 | } 31 | archiveFileName = "assembler.jar" 32 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 33 | } 34 | 35 | tasks.register('copyBuild', Copy) { 36 | dependsOn tasks.jar 37 | 38 | from tasks.jar.outputs 39 | into file("$rootDir/lib") 40 | } 41 | 42 | tasks.named("build") { finalizedBy("copyBuild") } 43 | 44 | distributions { 45 | main { 46 | distributionBaseName.set('proguard-assembler') 47 | contents { 48 | into("$rootDir/lib") { 49 | from jar.outputs 50 | } 51 | into("docs") { 52 | from("$rootDir/docs/md") { 53 | includeEmptyDirs = false 54 | include '**/*.md' 55 | } 56 | } 57 | from(rootDir) { 58 | include "$rootDir/bin/" 59 | include 'LICENSE' 60 | } 61 | } 62 | } 63 | } 64 | 65 | distTar { 66 | compression = Compression.GZIP 67 | archiveExtension.set('tar.gz') 68 | } 69 | 70 | clean { 71 | delete file("$rootDir/lib") 72 | } 73 | -------------------------------------------------------------------------------- /pga-cli/src/main/java/com/guardsquare/proguard/assembler/AssemblerCli.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.assembler; 19 | 20 | import com.guardsquare.proguard.disassembler.io.JbcDataEntryWriter; 21 | import com.guardsquare.proguard.assembler.io.JbcReader; 22 | import proguard.classfile.*; 23 | import proguard.classfile.attribute.visitor.AllAttributeVisitor; 24 | import proguard.classfile.editor.ConstantPoolSorter; 25 | import proguard.classfile.util.*; 26 | import proguard.classfile.visitor.*; 27 | import proguard.io.*; 28 | import proguard.preverify.CodePreverifier; 29 | import proguard.util.*; 30 | 31 | import java.io.*; 32 | 33 | /** 34 | * Main class for the ProGuard Disassembler and Assembler. 35 | * 36 | * @author Joachim Vandersmissen 37 | */ 38 | public class AssemblerCli 39 | { 40 | private static final byte[] JMOD_HEADER = new byte[] { 'J', 'M', 1, 0 }; 41 | private static final String JMOD_CLASS_FILE_PREFIX = "classes/"; 42 | 43 | 44 | public static void main(String[] args) throws IOException 45 | { 46 | if (args.length < 2 || args.length > 3) 47 | { 48 | System.out.println("ProGuard Assembler: assembles and disassembles Java class files."); 49 | System.out.println("Usage:"); 50 | System.out.println(" java proguard.Assembler [] "); 51 | System.out.println("The input and the output can be .class/.jbc/.jar/.jmod files or directories,"); 52 | System.out.println("where .jbc files contain disassembled Java bytecode."); 53 | System.out.println("The classpath (with runtime classes and library classes) is only necessary for preverifying assembled code."); 54 | System.exit(1); 55 | } 56 | 57 | int index = 0; 58 | 59 | String[] libraryPath = args.length >= 3 ? 60 | args[index++].split(System.getProperty("path.separator")) : null; 61 | String inputFileName = args[index++]; 62 | String outputFileName = args[index++]; 63 | 64 | ClassPool libraryClassPool = new ClassPool(); 65 | ClassPool programClassPool = new ClassPool(); 66 | 67 | // Read the libraries. 68 | if (libraryPath != null) 69 | { 70 | readLibraries(libraryPath, libraryClassPool); 71 | } 72 | 73 | // Read the actual input. 74 | DataEntrySource inputSource = 75 | source(inputFileName); 76 | 77 | System.out.println("Reading input file ["+inputFileName+"]..."); 78 | 79 | readInput(inputSource, 80 | libraryClassPool, 81 | programClassPool); 82 | 83 | // Preverify the program class pool. 84 | if (libraryPath != null && programClassPool.size() > 0) 85 | { 86 | System.out.println("Preverifying assembled class files..."); 87 | 88 | preverify(libraryClassPool, 89 | programClassPool); 90 | } 91 | 92 | // Clean up the program classes. 93 | programClassPool.classesAccept( 94 | new ConstantPoolSorter()); 95 | 96 | // Write out the output. 97 | System.out.println("Writing output file ["+outputFileName+"]..."); 98 | 99 | writeOutput(inputSource, 100 | outputFileName, 101 | libraryClassPool, 102 | programClassPool); 103 | } 104 | 105 | 106 | /** 107 | * Reads the specified libraries as library classes in the given class pool. 108 | */ 109 | private static void readLibraries(String[] libraryPath, 110 | ClassPool libraryClassPool) 111 | throws IOException 112 | { 113 | for (String libraryFileName : libraryPath) 114 | { 115 | System.out.println("Reading library file ["+libraryFileName+"]..."); 116 | 117 | // Any libraries go into the library class pool. 118 | DataEntryReader libraryClassReader = 119 | new ClassReader(true, false, false, false, null, 120 | new ClassPoolFiller(libraryClassPool)); 121 | 122 | DataEntrySource librarySource = 123 | source(libraryFileName); 124 | 125 | librarySource.pumpDataEntries(reader(libraryClassReader, 126 | null, 127 | null)); 128 | } 129 | } 130 | 131 | 132 | /** 133 | * Reads the specified input as program classes (for .jbc files) and 134 | * library classes (for .class files) in the given class pools. 135 | */ 136 | private static void readInput(DataEntrySource inputSource, 137 | ClassPool libraryClassPool, 138 | ClassPool programClassPool) 139 | throws IOException 140 | { 141 | // Any class files go into the library class pool. 142 | DataEntryReader classReader = 143 | new ClassReader(false, false, false, false, null, 144 | new ClassPoolFiller(libraryClassPool)); 145 | 146 | // Any jbc files go into the program class pool. 147 | DataEntryReader jbcReader = 148 | new JbcReader( 149 | new ClassPoolFiller(programClassPool)); 150 | 151 | DataEntryReader reader = 152 | reader(classReader, 153 | jbcReader, 154 | null); 155 | 156 | inputSource.pumpDataEntries(reader); 157 | } 158 | 159 | 160 | /** 161 | * Preverifies the program classes in the given class pools. 162 | */ 163 | private static void preverify(ClassPool libraryClassPool, 164 | ClassPool programClassPool) 165 | { 166 | programClassPool.classesAccept(new ClassReferenceInitializer(programClassPool, libraryClassPool)); 167 | programClassPool.classesAccept(new ClassSuperHierarchyInitializer(programClassPool, libraryClassPool)); 168 | libraryClassPool.classesAccept(new ClassReferenceInitializer(programClassPool, libraryClassPool)); 169 | libraryClassPool.classesAccept(new ClassSuperHierarchyInitializer(programClassPool, libraryClassPool)); 170 | 171 | programClassPool.classesAccept( 172 | new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_6, 173 | new AllMethodVisitor( 174 | new AllAttributeVisitor( 175 | new CodePreverifier(false))))); 176 | } 177 | 178 | 179 | /** 180 | * Reads the specified input, replacing .class files by .jbc files and 181 | * vice versa, and writing them to the specified output. 182 | */ 183 | private static void writeOutput(DataEntrySource inputSource, 184 | String outputFileName, 185 | ClassPool libraryClassPool, 186 | ClassPool programClassPool) 187 | throws IOException 188 | { 189 | DataEntryWriter writer = writer(outputFileName); 190 | 191 | // Write out class files as jbc files. 192 | DataEntryWriter classAsJbcWriter = 193 | new RenamedDataEntryWriter(new ConcatenatingStringFunction( 194 | new SuffixRemovingStringFunction(".class"), 195 | new ConstantStringFunction(".jbc")), 196 | new JbcDataEntryWriter(libraryClassPool, writer)); 197 | 198 | // Write out jbc files as class files. 199 | DataEntryWriter jbcAsClassWriter = 200 | new RenamedDataEntryWriter(new ConcatenatingStringFunction( 201 | new SuffixRemovingStringFunction(".jbc"), 202 | new ConstantStringFunction(".class")), 203 | new ClassDataEntryWriter(programClassPool, writer)); 204 | 205 | // Read the input again, writing out disassembled/assembled/preverified 206 | // files. 207 | inputSource.pumpDataEntries(reader(new IdleRewriter(classAsJbcWriter), 208 | new IdleRewriter(jbcAsClassWriter), 209 | new DataEntryCopier(writer))); 210 | 211 | writer.close(); 212 | } 213 | 214 | 215 | /** 216 | * Creates a data entry source for the given file name. 217 | */ 218 | private static DataEntrySource source(String inputFileName) 219 | { 220 | boolean isJar = inputFileName.endsWith(".jar"); 221 | boolean isJmod = inputFileName.endsWith(".jmod"); 222 | boolean isJbc = inputFileName.endsWith(".jbc"); 223 | boolean isClass = inputFileName.endsWith(".class"); 224 | 225 | File inputFile = new File(inputFileName); 226 | 227 | return isJar || 228 | isJmod || 229 | isJbc || 230 | isClass ? 231 | new FileSource(inputFile) : 232 | new DirectorySource(inputFile); 233 | } 234 | 235 | 236 | /** 237 | * Creates a data entry reader that unpacks archives if necessary and sends 238 | * data entries to given relevant readers. 239 | */ 240 | private static DataEntryReader reader(DataEntryReader classReader, 241 | DataEntryReader jbcReader, 242 | DataEntryReader resourceReader) 243 | { 244 | // Handle class files and resource files. 245 | DataEntryReader reader = 246 | new FilteredDataEntryReader(new DataEntryNameFilter(new ExtensionMatcher(".class")), 247 | classReader, 248 | resourceReader); 249 | 250 | // Handle jbc files. 251 | reader = 252 | new FilteredDataEntryReader(new DataEntryNameFilter(new ExtensionMatcher(".jbc")), 253 | jbcReader, 254 | reader); 255 | 256 | // Strip the "classes/" prefix from class/jbc files inside jmod files. 257 | DataEntryReader prefixStrippingReader = 258 | new FilteredDataEntryReader(new DataEntryNameFilter( 259 | new OrMatcher( 260 | new ExtensionMatcher(".class"), 261 | new ExtensionMatcher(".jbc"))), 262 | new PrefixStrippingDataEntryReader(JMOD_CLASS_FILE_PREFIX, reader), 263 | reader); 264 | 265 | // Unpack jmod files. 266 | reader = 267 | new FilteredDataEntryReader(new DataEntryNameFilter(new ExtensionMatcher(".jmod")), 268 | new JarReader(true, prefixStrippingReader), 269 | reader); 270 | 271 | // Unpack jar files. 272 | reader = 273 | new FilteredDataEntryReader(new DataEntryNameFilter(new ExtensionMatcher(".jar")), 274 | new JarReader(reader), 275 | reader); 276 | 277 | return reader; 278 | } 279 | 280 | 281 | /** 282 | * Creates a data entry writer for the given file name that creates archives 283 | * if necessary. 284 | */ 285 | private static DataEntryWriter writer(String outputFileName) 286 | { 287 | boolean isJar = outputFileName.endsWith(".jar"); 288 | boolean isJmod = outputFileName.endsWith(".jmod"); 289 | boolean isJbc = outputFileName.endsWith(".jbc"); 290 | boolean isClass = outputFileName.endsWith(".class"); 291 | 292 | File outputFile = new File(outputFileName); 293 | 294 | DataEntryWriter writer = 295 | isJar || 296 | isJmod || 297 | isJbc || 298 | isClass ? 299 | new FixedFileWriter(outputFile) : 300 | new DirectoryWriter(outputFile); 301 | 302 | // Pack jar files. 303 | if (isJar) 304 | { 305 | writer = 306 | new JarWriter( 307 | new ZipWriter(writer)); 308 | } 309 | 310 | // Pack jmod files. 311 | else if (isJmod) 312 | { 313 | writer = 314 | new JarWriter( 315 | new ZipWriter(null, 1, false, 0, JMOD_HEADER, 316 | writer)); 317 | 318 | // Add the "classes/" prefix to class/jbc files inside jmod files. 319 | writer = 320 | new FilteredDataEntryWriter(new DataEntryNameFilter( 321 | new OrMatcher( 322 | new ExtensionMatcher(".class"), 323 | new ExtensionMatcher(".jbc"))), 324 | new PrefixAddingDataEntryWriter(JMOD_CLASS_FILE_PREFIX, writer), 325 | writer); 326 | } 327 | 328 | return writer; 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /pga-lib/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'java-library' 4 | id 'maven-publish' 5 | id 'signing' 6 | 7 | id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" 8 | id 'com.adarshr.test-logger' 9 | id 'jacoco' 10 | id "org.jlleitschuh.gradle.ktlint" 11 | id "java-test-fixtures" 12 | } 13 | 14 | java { 15 | toolchain { 16 | languageVersion.set(JavaLanguageVersion.of(8)) 17 | } 18 | 19 | withSourcesJar() 20 | withJavadocJar() 21 | } 22 | 23 | dependencies { 24 | api "com.guardsquare:proguard-core:${proguardCoreVersion}" 25 | 26 | testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" 27 | testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" 28 | testImplementation 'dev.zacsweers.kctfork:core:0.3.0' 29 | testImplementation "io.kotest:kotest-runner-junit5-jvm:$kotestVersion" // for kotest framework 30 | testImplementation "io.kotest:kotest-assertions-core-jvm:$kotestVersion" // for kotest core jvm assertions 31 | testImplementation "io.kotest:kotest-property-jvm:$kotestVersion" // for kotest property test 32 | testImplementation 'io.mockk:mockk:1.13.5' // for mocking 33 | 34 | testImplementation(testFixtures("com.guardsquare:proguard-core:${proguardCoreVersion}")) { 35 | exclude group: 'com.guardsquare', module: 'proguard-core' 36 | } 37 | } 38 | 39 | // Early access automatic downloads are not yet supported: 40 | // https://github.com/gradle/gradle/issues/14814 41 | // But it will work if e.g. Java N-ea is pre-installed 42 | def javaVersionsForTest = 9..19 43 | 44 | test { 45 | useJUnitPlatform() 46 | } 47 | 48 | jacocoTestReport { 49 | // Define which classes need to be monitored 50 | def sources = files(sourceSets.main.allSource.srcDirs) 51 | sourceDirectories.setFrom(sources) 52 | additionalSourceDirs.setFrom(sources) 53 | sourceDirectories.setFrom(sources) 54 | def classes = files(sourceSets.main.output.classesDirs) 55 | classDirectories.setFrom(classes) 56 | executionData.setFrom project.fileTree(dir: '.', include: '**/build/jacoco/*.exec') 57 | reports { 58 | xml.required = true 59 | csv.required = true 60 | html.destination file("${buildDir}/reports/coverage") 61 | } 62 | } 63 | 64 | publishing { 65 | publications { 66 | mavenJava(MavenPublication) { publication -> 67 | groupId = 'com.guardsquare' 68 | artifactId = 'proguard-assembler' 69 | version = version 70 | pom { 71 | name = "ProGuard Assembler and Disassembler" 72 | description = 'The ProGuard Assembler and Disassembler can assemble and disassemble Java class files.' 73 | url = "https://github.com/Guardsquare/kotlin-metadata-printer" 74 | licenses { 75 | license { 76 | name = 'Apache License Version 2.0' 77 | url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' 78 | distribution = 'repo' 79 | } 80 | } 81 | issueManagement { 82 | system = 'Github Tracker' 83 | url = 'https://github.com/Guardsquare/proguard-assembler/issues' 84 | } 85 | scm { 86 | url = 'https://github.com/Guardsquare/proguard-assembler.git' 87 | connection = 'scm:git:https://github.com/Guardsquare/proguard-assembler.git' 88 | } 89 | developers { 90 | developer { 91 | id = 'james.hamilton' 92 | name = 'James Hamilton' 93 | organization = 'Guardsquare' 94 | organizationUrl = 'https://www.guardsquare.com/' 95 | roles = ['Project Administrator', 'Developer'] 96 | } 97 | } 98 | } 99 | 100 | from components.java 101 | 102 | if (project.hasProperty('PROGUARD_SIGNING_KEY')) { 103 | // We use in-memory ascii-armored keys 104 | // See https://docs.gradle.org/current/userguide/signing_plugin.html#sec:in-memory-keys 105 | signing { 106 | String key = project.findProperty('PROGUARD_SIGNING_KEY') 107 | String password = project.findProperty('PROGUARD_SIGNING_PASSWORD') 108 | useInMemoryPgpKeys(key, password) 109 | sign publication 110 | } 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/assembler/AssemblyConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.assembler; 19 | 20 | import java.text.*; 21 | import java.util.Locale; 22 | 23 | public final class AssemblyConstants 24 | { 25 | public static final String JBC_EXTENSION = ".jbc"; 26 | 27 | public static final char ATTRIBUTES_OPEN = '['; 28 | public static final char ATTRIBUTES_CLOSE = ']'; 29 | public static final char EQUALS = '='; 30 | public static final char COLON = ':'; 31 | public static final char REFERENCE_SEPARATOR = '#'; 32 | public static final char SPECIAL_METHOD_PREFIX = '<'; 33 | public static final char SPECIAL_METHOD_SUFFIX = '>'; 34 | public static final char STRING_QUOTE = '"'; 35 | public static final char CHAR_QUOTE = '\''; 36 | 37 | public static final String ACC_ANNOTATION = "annotation"; 38 | 39 | public static final String TYPE_STRING = "String"; 40 | public static final String TYPE_CLASS = "Class"; 41 | public static final String TYPE_METHOD_HANDLE = "MethodHandle"; 42 | public static final String TYPE_METHOD_TYPE = "MethodType"; 43 | public static final String TYPE_DYNAMIC = "Dynamic"; 44 | public static final String TYPE_ANNOTATION = "Annotation"; 45 | public static final String TYPE_ENUM = "Enum"; 46 | public static final String TYPE_ARRAY = "Array"; 47 | public static final String TYPE_DOUBLE = "D"; 48 | public static final String TYPE_FLOAT = "F"; 49 | public static final String TYPE_LONG = "L"; 50 | 51 | public static final String REF_GET_FIELD = "getfield"; 52 | public static final String REF_GET_STATIC = "getstatic"; 53 | public static final String REF_PUT_FIELD = "putfield"; 54 | public static final String REF_PUT_STATIC = "putstatic"; 55 | public static final String REF_INVOKE_VIRTUAL = "invokevirtual"; 56 | public static final String REF_INVOKE_STATIC = "invokestatic"; 57 | public static final String REF_INVOKE_SPECIAL = "invokespecial"; 58 | public static final String REF_NEW_INVOKE_SPECIAL = "newinvokespecial"; 59 | public static final String REF_INVOKE_INTERFACE = "invokeinterface"; 60 | 61 | public static final String TARGET_TYPE_P_A_RAMETER_GENERIC_CLASS = "parameter_generic_class"; 62 | public static final String TARGET_TYPE_P_A_RAMETER_GENERIC_METHOD = "parameter_generic_method"; 63 | public static final String TARGET_TYPE_E_X_TENDS = "extends"; 64 | public static final String TARGET_TYPE_B_O_UND_GENERIC_CLASS = "bound_generic_class"; 65 | public static final String TARGET_TYPE_B_O_UND_GENERIC_METHOD = "bound_generic_method"; 66 | public static final String TARGET_TYPE_F_I_ELD = "field"; 67 | public static final String TARGET_TYPE_R_E_TURN = "return"; 68 | public static final String TARGET_TYPE_R_E_CEIVER = "receiver"; 69 | public static final String TARGET_TYPE_P_A_RAMETER = "parameter"; 70 | public static final String TARGET_TYPE_T_H_ROWS = "throws"; 71 | public static final String TARGET_TYPE_L_O_CAL_VARIABLE = "local_variable"; 72 | public static final String TARGET_TYPE_R_E_SOURCE_VARIABLE = "resource_variable"; 73 | public static final String TARGET_TYPE_C_A_TCH = "catch"; 74 | public static final String TARGET_TYPE_I_N_STANCE_OF = "instance_of"; 75 | public static final String TARGET_TYPE_N_E_W = "new"; 76 | public static final String TARGET_TYPE_M_E_THOD_REFERENCE_NEW = "method_reference_new"; 77 | public static final String TARGET_TYPE_M_E_THOD_REFERENCE = "method_reference"; 78 | public static final String TARGET_TYPE_C_A_ST = "cast"; 79 | public static final String TARGET_TYPE_A_R_GUMENT_GENERIC_METHOD_NEW = "argument_generic_method_new"; 80 | public static final String TARGET_TYPE_A_R_GUMENT_GENERIC_METHOD = "argument_generic_method"; 81 | public static final String TARGET_TYPE_A_R_GUMENT_GENERIC_METHOD_REFERENCE_NEW = "argument_generic_method_reference_new"; 82 | public static final String TARGET_TYPE_A_R_GUMENT_GENERIC_METHOD_REFERENCE = "argument_generic_method_reference"; 83 | 84 | public static final String TYPE_PATH_ARRAY = "array"; 85 | public static final String TYPE_PATH_NESTED = "inner_type"; 86 | public static final String TYPE_PATH_TYPE_ARGUMENT_BOUND = "wildcard"; 87 | public static final String TYPE_PATH_TYPE_ARGUMENT = "type_argument"; 88 | 89 | public static final String IMPORT = "import"; 90 | public static final String CLASS = "class"; 91 | public static final String EXTENDS = "extends"; 92 | public static final String IMPLEMENTS = "implements"; 93 | public static final String THROWS = "throws"; 94 | public static final String CATCH = "catch"; 95 | public static final String CASE = "case"; 96 | public static final String DEFAULT = "default"; 97 | public static final String REQUIRES = "requires"; 98 | public static final String EXPORTS = "exports"; 99 | public static final String OPENS = "opens"; 100 | public static final String USES = "uses"; 101 | public static final String PROVIDES = "provides"; 102 | public static final String TRUE = "true"; 103 | public static final String FALSE = "false"; 104 | 105 | public static final char BODY_OPEN = '{'; 106 | public static final char BODY_CLOSE = '}'; 107 | public static final char STATEMENT_END = ';'; 108 | 109 | public static final String VERSION = "version"; 110 | public static final String INIT = "init"; 111 | public static final String CLINIT = "clinit"; 112 | public static final String LINE = "line"; 113 | public static final String ANY = "any"; 114 | public static final String TO = "to"; 115 | public static final String WITH = "with"; 116 | public static final String AS = "as"; 117 | public static final String IN = "in"; 118 | public static final String LABEL = "label"; 119 | public static final String LOCAL_VAR_START = "startlocalvar"; 120 | public static final String LOCAL_VAR_END = "endlocalvar"; 121 | public static final String LOCAL_VAR_TYPE_START = "startlocalvartype"; 122 | public static final String LOCAL_VAR_TYPE_END = "endlocalvartype"; 123 | 124 | public static final DecimalFormat DOUBLE_TO_STRING = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); 125 | 126 | static { 127 | DOUBLE_TO_STRING.setMaximumFractionDigits(340); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/assembler/ClassMembersParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.assembler; 19 | 20 | import proguard.classfile.*; 21 | import proguard.classfile.attribute.*; 22 | import proguard.classfile.editor.*; 23 | import proguard.classfile.util.*; 24 | import proguard.classfile.visitor.*; 25 | import proguard.util.ArrayUtil; 26 | 27 | /** 28 | * Parses ProgramClass members. 29 | * 30 | * @author Joachim Vandersmissen 31 | */ 32 | public class ClassMembersParser 33 | implements ClassVisitor, 34 | MemberVisitor 35 | { 36 | private final Parser p; 37 | private final ConstantPoolEditor cpe; 38 | 39 | 40 | /** 41 | * Constructs a new ClassMembersParser that uses a Parser and a 42 | * ConstantPoolEditor. 43 | * 44 | * @param p the Parser to use to parse basic structures. 45 | * @param cpe the ConstantPoolEditor to use to add Constants to the constant 46 | * pool. 47 | */ 48 | public ClassMembersParser(Parser p, ConstantPoolEditor cpe) 49 | { 50 | this.p = p; 51 | this.cpe = cpe; 52 | } 53 | 54 | 55 | // Implementations for ClassVisitor. 56 | 57 | public void visitAnyClass(Clazz clazz) {} 58 | 59 | 60 | public void visitProgramClass(ProgramClass programClass) 61 | { 62 | ClassEditor classEditor = new ClassEditor(programClass); 63 | while (!p.nextTtypeEquals(AssemblyConstants.BODY_CLOSE)) 64 | { 65 | // We need to parse a lot of information before we know which 66 | // class member we're declaring. 67 | int accessFlags = p.expectAccessFlags(); 68 | if (p.nextTtypeEquals(AssemblyConstants.ATTRIBUTES_OPEN) || 69 | p.nextTtypeEquals(AssemblyConstants.BODY_OPEN)) 70 | { 71 | // We know we're declaring a method now. 72 | p.pushBack(); 73 | 74 | ProgramMethod method = 75 | new ProgramMethod(accessFlags, 76 | cpe.addUtf8Constant(ClassConstants.METHOD_NAME_CLINIT), 77 | cpe.addUtf8Constant(ClassConstants.METHOD_TYPE_CLINIT), 78 | null); 79 | method.accept(programClass, this); 80 | 81 | classEditor.addMethod(method); 82 | } 83 | else 84 | { 85 | String type = p.expectType("class member type"); 86 | String name = p.expectMethodName("class member name"); 87 | if (p.nextTtypeEquals(JavaTypeConstants.METHOD_ARGUMENTS_OPEN)) 88 | { 89 | // We know we're declaring a method now. 90 | MethodParametersAttribute methodParametersAttribute = 91 | new MethodParametersAttribute(); 92 | methodParametersAttribute.u2attributeNameIndex = 93 | cpe.addUtf8Constant(Attribute.METHOD_PARAMETERS); 94 | methodParametersAttribute.parameters = 95 | new ParameterInfo[0]; 96 | 97 | ProgramMethod method = 98 | new ProgramMethod(accessFlags, 99 | cpe.addUtf8Constant(name), 100 | cpe.addUtf8Constant(expectMethodArguments(methodParametersAttribute) + type), 101 | null); 102 | method.accept(programClass, this); 103 | 104 | if (methodParametersAttribute.u1parametersCount > 0) 105 | { 106 | new AttributesEditor(programClass, method, false).addAttribute(methodParametersAttribute); 107 | } 108 | 109 | classEditor.addMethod(method); 110 | } 111 | else 112 | { 113 | // We know we're declaring a field now. 114 | ProgramField field = 115 | new ProgramField(accessFlags, 116 | cpe.addUtf8Constant(name), 117 | cpe.addUtf8Constant(type), 118 | null); 119 | field.accept(programClass, this); 120 | 121 | classEditor.addField(field); 122 | } 123 | } 124 | } 125 | } 126 | 127 | 128 | // Implementations for MemberVisitor. 129 | 130 | public void visitProgramField(ProgramClass programClass, ProgramField programField) 131 | { 132 | if (p.nextTtypeEquals(AssemblyConstants.EQUALS)) 133 | { 134 | ConstantValueAttribute constantValueAttribute = 135 | new ConstantValueAttribute(cpe.addUtf8Constant(Attribute.CONSTANT_VALUE), 136 | p.expectLoadableConstant(programClass, cpe, new ConstantParser(p, cpe))); 137 | 138 | new AttributesEditor(programClass, programField, false).addAttribute(constantValueAttribute); 139 | } 140 | 141 | if (p.nextTtypeEquals(AssemblyConstants.ATTRIBUTES_OPEN)) 142 | { 143 | programField.accept(programClass, new AttributesParser(p, cpe)); 144 | } 145 | 146 | p.expect(AssemblyConstants.STATEMENT_END, "field end"); 147 | } 148 | 149 | 150 | public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) 151 | { 152 | 153 | if (p.expectIfNextTtypeEqualsWord(AssemblyConstants.THROWS)) 154 | { 155 | ExceptionsAttribute exceptionsAttribute = 156 | new ExceptionsAttribute(); 157 | exceptionsAttribute.u2attributeNameIndex = 158 | cpe.addUtf8Constant(Attribute.EXCEPTIONS); 159 | exceptionsAttribute.u2exceptionIndexTable = new int[0]; 160 | 161 | do 162 | { 163 | String exceptionType = 164 | ClassUtil.internalClassName(p.expectWord("exception class")); 165 | 166 | exceptionsAttribute.u2exceptionIndexTable = 167 | ArrayUtil.add(exceptionsAttribute.u2exceptionIndexTable, 168 | exceptionsAttribute.u2exceptionIndexTableLength++, 169 | cpe.addClassConstant(exceptionType, null)); 170 | } while (p.nextTtypeEquals(JavaTypeConstants.METHOD_ARGUMENTS_SEPARATOR)); 171 | 172 | new AttributesEditor(programClass, programMethod, false).addAttribute(exceptionsAttribute); 173 | } 174 | 175 | if (p.nextTtypeEquals(AssemblyConstants.ATTRIBUTES_OPEN)) 176 | { 177 | programMethod.accept(programClass, new AttributesParser(p, cpe)); 178 | } 179 | 180 | if (p.nextTtypeEquals(AssemblyConstants.BODY_OPEN)) 181 | { 182 | programMethod.accept(programClass, new InstructionsParser(p, cpe, new CodeAttributeComposer(true, true, false))); 183 | } 184 | else 185 | { 186 | p.expect(AssemblyConstants.STATEMENT_END, "method end"); 187 | } 188 | } 189 | 190 | 191 | // Small utility methods. 192 | 193 | /** 194 | * Parses ProgramMethod arguments. This method advances the Parser. 195 | * 196 | * @param methodParametersAttribute the MethodParametersAttribute to add the 197 | * method parameters to. 198 | * @return the method arguments descriptor. 199 | */ 200 | private String expectMethodArguments(MethodParametersAttribute methodParametersAttribute) 201 | { 202 | StringBuilder methodArguments = new StringBuilder(); 203 | methodArguments.append(JavaTypeConstants.METHOD_ARGUMENTS_OPEN); 204 | if (p.nextTtypeEquals(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE)) 205 | { 206 | return methodArguments.append(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE).toString(); 207 | } 208 | 209 | while (true) 210 | { 211 | // Syntactic sugar: parameters access/names in method descriptor. 212 | ParameterInfo parameterInfo = new ParameterInfo(); 213 | parameterInfo.u2accessFlags = p.expectAccessFlags(); 214 | methodArguments.append(p.expectType("method parameter type")); 215 | 216 | // Name index is optional. 217 | if (p.nextTtypeEqualsWord()) 218 | { 219 | parameterInfo.u2nameIndex = cpe.addUtf8Constant(p.sval); 220 | } 221 | 222 | methodParametersAttribute.parameters = 223 | ArrayUtil.add(methodParametersAttribute.parameters, methodParametersAttribute.u1parametersCount++, parameterInfo); 224 | 225 | if (p.nextTtypeEquals(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE)) 226 | { 227 | break; 228 | } 229 | 230 | p.expect(JavaTypeConstants.METHOD_ARGUMENTS_SEPARATOR, "method arguments separator"); 231 | } 232 | 233 | // Check if any of the parameters actually has a name or access flags. 234 | for (int index = 0; index < methodParametersAttribute.u1parametersCount; index++) 235 | { 236 | ParameterInfo parameterInfo = methodParametersAttribute.parameters[index]; 237 | if (parameterInfo.u2accessFlags != 0 || 238 | parameterInfo.u2nameIndex != 0) 239 | { 240 | return methodArguments.append(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE).toString(); 241 | } 242 | } 243 | 244 | // No parameters with name or access flag so we set the count to 0. 245 | methodParametersAttribute.u1parametersCount = 0; 246 | return methodArguments.append(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE).toString(); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/assembler/ClassParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.assembler; 19 | 20 | import proguard.classfile.AccessConstants; 21 | import proguard.classfile.ClassConstants; 22 | import proguard.classfile.Clazz; 23 | import proguard.classfile.JavaTypeConstants; 24 | import proguard.classfile.JavaVersionConstants; 25 | import proguard.classfile.ProgramClass; 26 | import proguard.classfile.ProgramField; 27 | import proguard.classfile.ProgramMethod; 28 | import proguard.classfile.attribute.Attribute; 29 | import proguard.classfile.constant.Constant; 30 | import proguard.classfile.editor.ClassEditor; 31 | import proguard.classfile.editor.ConstantPoolEditor; 32 | import proguard.classfile.util.ClassUtil; 33 | import proguard.classfile.visitor.ClassVisitor; 34 | 35 | /** 36 | * Parses a ProgramClass. 37 | * 38 | * @author Joachim Vandersmissen 39 | */ 40 | public class ClassParser 41 | implements ClassVisitor 42 | { 43 | private final Parser p; 44 | 45 | 46 | /** 47 | * Constructs a new ClassParser that uses a Parser. 48 | * 49 | * @param p the Parser to use to parse basic structures. 50 | */ 51 | public ClassParser(Parser p) 52 | { 53 | this.p = p; 54 | } 55 | 56 | 57 | // Implementations for ClassVisitor. 58 | 59 | public void visitAnyClass(Clazz clazz) {} 60 | 61 | 62 | public void visitProgramClass(ProgramClass programClass) 63 | { 64 | programClass.u2constantPoolCount = 1; 65 | programClass.constantPool = new Constant[1]; 66 | programClass.u2interfaces = new int[0]; 67 | programClass.fields = new ProgramField[0]; 68 | programClass.methods = new ProgramMethod[0]; 69 | programClass.attributes = new Attribute[0]; 70 | 71 | while (p.nextTtypeEqualsWord()) 72 | { 73 | String keyword = p.sval; 74 | if (AssemblyConstants.IMPORT.equals(keyword)) 75 | { 76 | String className = p.expectWord("class name"); 77 | p.expect(AssemblyConstants.STATEMENT_END, "import end"); 78 | int index = className.lastIndexOf(JavaTypeConstants.PACKAGE_SEPARATOR); 79 | if (index > -1) 80 | { 81 | p.imports.put(className.substring(index + 1), className); 82 | } 83 | } 84 | else if (AssemblyConstants.VERSION.equals(keyword)) 85 | { 86 | programClass.u4version = 87 | ClassUtil.internalClassVersion(AssemblyConstants.DOUBLE_TO_STRING.format(p.expectNumber("version number"))); 88 | p.expect(AssemblyConstants.STATEMENT_END, "version end"); 89 | } 90 | else 91 | { 92 | p.pushBack(); 93 | break; 94 | } 95 | } 96 | 97 | if (programClass.u4version == 0) 98 | { 99 | programClass.u4version = ClassUtil.internalClassVersion(JavaVersionConstants.CLASS_VERSION_1_8); 100 | } 101 | 102 | ClassEditor classEditor = new ClassEditor(programClass); 103 | ConstantPoolEditor cpe = new ConstantPoolEditor(programClass); 104 | 105 | programClass.u2accessFlags = p.expectClassAccessFlags(); 106 | programClass.u2thisClass = 107 | cpe.addClassConstant(ClassUtil.internalClassName(p.expectWord("this class")), null); 108 | 109 | // Syntactic sugar: extends in interfaces define bytecode interfaces. 110 | if ((programClass.u2accessFlags & AccessConstants.INTERFACE) != 0) 111 | { 112 | if (p.expectIfNextTtypeEqualsWord(AssemblyConstants.EXTENDS)) 113 | { 114 | expectInterfaces(classEditor, cpe); 115 | } 116 | } 117 | else 118 | { 119 | if (p.nextTtypeEqualsWord()) { 120 | if (p.sval.equals(AssemblyConstants.EXTENDS)) 121 | { 122 | programClass.u2superClass = 123 | cpe.addClassConstant(ClassUtil.internalClassName(p.expectWord("super class")), null); 124 | } 125 | else 126 | { 127 | p.pushBack(); 128 | } 129 | } 130 | 131 | while (p.expectIfNextTtypeEqualsWord(AssemblyConstants.IMPLEMENTS)) 132 | { 133 | expectInterfaces(classEditor, cpe); 134 | } 135 | } 136 | 137 | // Syntactic sugar: default superclasses. 138 | if (programClass.u2superClass == 0) 139 | { 140 | if ((programClass.u2accessFlags & AccessConstants.ENUM) != 0) 141 | { 142 | // Syntactic sugar: default java.lang.Enum superclass for enums. 143 | programClass.u2superClass = 144 | cpe.addClassConstant(ClassConstants.NAME_JAVA_LANG_ENUM, null); 145 | } 146 | else if ((programClass.u2accessFlags & AccessConstants.MODULE) == 0 && 147 | !ClassConstants.NAME_JAVA_LANG_OBJECT.equals(programClass.getName())) 148 | { 149 | // Syntactic sugar: default java.lang.Object superclass for any 150 | // other class type *except* modules and java.lang.Object itself. 151 | programClass.u2superClass = 152 | cpe.addClassConstant(ClassConstants.NAME_JAVA_LANG_OBJECT, null); 153 | } 154 | } 155 | 156 | // Syntactic sugar: default interface for annotations. 157 | if ((programClass.u2accessFlags & AccessConstants.ANNOTATION) != 0 && 158 | !containsInterface(programClass, ClassConstants.NAME_JAVA_LANG_ANNOTATION_ANNOTATION)) 159 | { 160 | classEditor.addInterface(cpe.addClassConstant(ClassConstants.NAME_JAVA_LANG_ANNOTATION_ANNOTATION, null)); 161 | } 162 | 163 | if (p.nextTtypeEquals(AssemblyConstants.ATTRIBUTES_OPEN)) 164 | { 165 | programClass.accept(new AttributesParser(p, cpe)); 166 | } 167 | 168 | if (p.nextTtypeEquals(AssemblyConstants.BODY_OPEN)) 169 | { 170 | programClass.accept(new ClassMembersParser(p, cpe)); 171 | } 172 | else 173 | { 174 | p.expect(AssemblyConstants.STATEMENT_END, "class end"); 175 | } 176 | } 177 | 178 | 179 | // Small utility methods. 180 | 181 | /** 182 | * Parses interfaces. 183 | * 184 | * @param classEditor The ClassEditor to add the interfaces to. 185 | * @param cpe The ConstantPoolEditor to use to add constants. 186 | */ 187 | private void expectInterfaces(ClassEditor classEditor, ConstantPoolEditor cpe) 188 | { 189 | do 190 | { 191 | classEditor.addInterface(cpe.addClassConstant(ClassUtil.internalClassName(p.expectWord("interface class")), null)); 192 | } while (p.nextTtypeEquals(',')); 193 | } 194 | 195 | 196 | /** 197 | * Returns whether a given ProgramClass contains an interface. 198 | * 199 | * @param programClass the ProgramClass to check. 200 | * @param interf the internal name of the interface. 201 | * @return true if the ProgramClass contains the interface, false otherwise. 202 | */ 203 | private boolean containsInterface(ProgramClass programClass, String interf) 204 | { 205 | for (int index = 0; index < programClass.u2interfacesCount; index++) 206 | { 207 | if (interf.equals(programClass.getInterfaceName(index))) 208 | { 209 | return true; 210 | } 211 | } 212 | 213 | return false; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/assembler/ConstantParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.assembler; 19 | 20 | import proguard.classfile.*; 21 | import proguard.classfile.constant.*; 22 | import proguard.classfile.constant.visitor.ConstantVisitor; 23 | import proguard.classfile.editor.*; 24 | import proguard.classfile.util.*; 25 | 26 | /** 27 | * Parses Constants, adds them to the constant pool and sets the index field. 28 | * 29 | * @author Joachim Vandersmissen 30 | */ 31 | public class ConstantParser 32 | implements ConstantVisitor 33 | { 34 | private static final int INTEGER_FALSE = 1; 35 | private static final int INTEGER_TRUE = 1; 36 | 37 | 38 | private final Parser p; 39 | private final ConstantPoolEditor cpe; 40 | 41 | private int index; 42 | 43 | 44 | /** 45 | * Constructs a new ConstantParser that uses a Parser and a 46 | * ConstantPoolEditor. 47 | * 48 | * @param p the Parser to use to parse basic structures. 49 | * @param cpe the ConstantPoolEditor to use to add Constants to the constant 50 | * pool. 51 | */ 52 | public ConstantParser(Parser p, ConstantPoolEditor cpe) 53 | { 54 | this.p = p; 55 | this.cpe = cpe; 56 | } 57 | 58 | 59 | public int getIndex() 60 | { 61 | return index; 62 | } 63 | 64 | 65 | // Implementations for ConstantVisitor. 66 | 67 | public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) 68 | { 69 | if (p.nextTtypeEqualsChar()) 70 | { 71 | if (p.sval.length() > 1) 72 | { 73 | throw new ParseException("Char value contains multiple characters.", p.lineno()); 74 | } 75 | 76 | index = cpe.addIntegerConstant((int) p.sval.charAt(0)); 77 | return; 78 | } 79 | 80 | if (p.nextTtypeEqualsNumber()) 81 | { 82 | index = cpe.addIntegerConstant((int) p.nval); 83 | return; 84 | } 85 | 86 | if (AssemblyConstants.TRUE.equals(p.expect(AssemblyConstants.TRUE, AssemblyConstants.FALSE))) 87 | { 88 | index = cpe.addIntegerConstant(INTEGER_TRUE); 89 | return; 90 | } 91 | 92 | // Keyword is FALSE 93 | index = cpe.addIntegerConstant(INTEGER_FALSE); 94 | } 95 | 96 | 97 | public void visitLongConstant(Clazz clazz, LongConstant longConstant) 98 | { 99 | index = cpe.addLongConstant((long) p.expectNumber("long value")); 100 | } 101 | 102 | 103 | public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant) 104 | { 105 | index = cpe.addFloatConstant((float) p.expectNumber("float value")); 106 | } 107 | 108 | 109 | public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant) 110 | { 111 | index = cpe.addDoubleConstant(p.expectNumber("double value")); 112 | } 113 | 114 | 115 | public void visitStringConstant(Clazz clazz, StringConstant stringConstant) 116 | { 117 | index = cpe.addStringConstant(p.expectString("string value"), null, null); 118 | } 119 | 120 | 121 | public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) 122 | { 123 | // Should only be used when an actual "string" (surrounded by quotes) is 124 | // expected. 125 | index = cpe.addUtf8Constant(p.expectString("string value")); 126 | } 127 | 128 | 129 | public void visitDynamicConstant(Clazz clazz, DynamicConstant dynamicConstant) 130 | { 131 | int bootstrapMethodAttributeIndex = 132 | (int) p.expectNumber("dynamic bootstrap method index"); 133 | String type = p.expectType("dynamic type"); 134 | String name = p.expectWord("dynamic name"); 135 | index = cpe.addDynamicConstant(bootstrapMethodAttributeIndex, 136 | cpe.addNameAndTypeConstant(name, type), 137 | null); 138 | } 139 | 140 | 141 | public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) 142 | { 143 | int bootstrapMethodAttributeIndex = 144 | (int) p.expectNumber("invokedynamic bootstrap method index"); 145 | String returnType = p.expectType("invokedynamic return type"); 146 | String name = p.expectMethodName("invokedynamic name"); 147 | String methodArgs = p.expectMethodArguments("invokedynamic arguments"); 148 | index = cpe.addInvokeDynamicConstant(bootstrapMethodAttributeIndex, 149 | cpe.addNameAndTypeConstant(name, methodArgs + returnType), 150 | null); 151 | } 152 | 153 | 154 | public void visitMethodHandleConstant(Clazz clazz, MethodHandleConstant methodHandleConstant) 155 | { 156 | String referenceKind = p.expectWord("reference kind"); 157 | int refKind; 158 | RefConstant refConstant; 159 | switch (referenceKind) 160 | { 161 | case AssemblyConstants.REF_GET_FIELD: refKind = MethodHandleConstant.REF_GET_FIELD; refConstant = new FieldrefConstant(); break; 162 | case AssemblyConstants.REF_GET_STATIC: refKind = MethodHandleConstant.REF_GET_STATIC; refConstant = new FieldrefConstant(); break; 163 | case AssemblyConstants.REF_PUT_FIELD: refKind = MethodHandleConstant.REF_PUT_FIELD; refConstant = new FieldrefConstant(); break; 164 | case AssemblyConstants.REF_PUT_STATIC: refKind = MethodHandleConstant.REF_PUT_STATIC; refConstant = new FieldrefConstant(); break; 165 | case AssemblyConstants.REF_INVOKE_VIRTUAL: refKind = MethodHandleConstant.REF_INVOKE_VIRTUAL; refConstant = new MethodrefConstant(); break; 166 | case AssemblyConstants.REF_INVOKE_STATIC: refKind = MethodHandleConstant.REF_INVOKE_STATIC; refConstant = new MethodrefConstant(); break; 167 | case AssemblyConstants.REF_INVOKE_SPECIAL: refKind = MethodHandleConstant.REF_INVOKE_SPECIAL; refConstant = new MethodrefConstant(); break; 168 | case AssemblyConstants.REF_NEW_INVOKE_SPECIAL: refKind = MethodHandleConstant.REF_NEW_INVOKE_SPECIAL; refConstant = new MethodrefConstant(); break; 169 | case AssemblyConstants.REF_INVOKE_INTERFACE: refKind = MethodHandleConstant.REF_INVOKE_INTERFACE; refConstant = new InterfaceMethodrefConstant(); break; 170 | default: throw new ParseException("Unknown reference kind " + referenceKind + ".", p.lineno()); 171 | } 172 | 173 | refConstant.accept(clazz, this); 174 | index = cpe.addMethodHandleConstant(refKind, index); 175 | } 176 | 177 | 178 | public void visitModuleConstant(Clazz clazz, ModuleConstant moduleConstant) 179 | { 180 | // Modules are not encoded in internal form like class and interface 181 | // names... 182 | index = cpe.addModuleConstant(p.expectWord("module name")); 183 | } 184 | 185 | 186 | public void visitPackageConstant(Clazz clazz, PackageConstant packageConstant) 187 | { 188 | index = cpe.addPackageConstant(ClassUtil.externalPackageName(p.expectWord("package name"))); 189 | } 190 | 191 | 192 | public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) 193 | { 194 | int classIndex; 195 | if (p.nextTtypeEquals(AssemblyConstants.REFERENCE_SEPARATOR)) 196 | { 197 | classIndex = ((ProgramClass) clazz).u2thisClass; 198 | } 199 | else 200 | { 201 | new ClassConstant().accept(clazz, this); 202 | classIndex = index; 203 | p.expect(AssemblyConstants.REFERENCE_SEPARATOR, "fieldref separator"); 204 | } 205 | 206 | String type = p.expectType("fieldref type"); 207 | String name = p.expectWord("fieldref name"); 208 | index = cpe.addFieldrefConstant(classIndex, name, type, null, null); 209 | } 210 | 211 | 212 | public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) 213 | { 214 | int classIndex; 215 | if (p.nextTtypeEquals(AssemblyConstants.REFERENCE_SEPARATOR)) 216 | { 217 | classIndex = ((ProgramClass) clazz).u2thisClass; 218 | } 219 | else 220 | { 221 | new ClassConstant().accept(clazz, this); 222 | classIndex = index; 223 | p.expect(AssemblyConstants.REFERENCE_SEPARATOR, "methodref separator"); 224 | } 225 | 226 | String returnType = p.expectType("methodref return type"); 227 | String name = p.expectMethodName("methodref name"); 228 | String methodArgs = p.expectMethodArguments("methodref arguments"); 229 | index = cpe.addMethodrefConstant(classIndex, name, methodArgs + returnType, null, null); 230 | } 231 | 232 | 233 | public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) 234 | { 235 | int classIndex; 236 | if (p.nextTtypeEquals(AssemblyConstants.REFERENCE_SEPARATOR)) 237 | { 238 | classIndex = ((ProgramClass) clazz).u2thisClass; 239 | } 240 | else 241 | { 242 | new ClassConstant().accept(clazz, this); 243 | classIndex = index; 244 | p.expect(AssemblyConstants.REFERENCE_SEPARATOR, "interfacemethodref separator"); 245 | } 246 | 247 | String returnType = p.expectType("interfacemethodref return type"); 248 | String name = p.expectMethodName("interfacemethodref name"); 249 | String methodArgs = p.expectMethodArguments("interfacemethodref arguments"); 250 | index = cpe.addInterfaceMethodrefConstant(classIndex, name, methodArgs + returnType, null, null); 251 | } 252 | 253 | 254 | public void visitClassConstant(Clazz clazz, ClassConstant classConstant) 255 | { 256 | index = cpe.addClassConstant(ClassUtil.internalClassTypeFromType(p.expectType("class name")), null); 257 | } 258 | 259 | 260 | public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant) 261 | { 262 | String returnType = p.expectType("method return type"); 263 | String methodArgs = p.expectMethodArguments("method arguments"); 264 | index = cpe.addMethodTypeConstant(methodArgs + returnType, null); 265 | } 266 | 267 | 268 | public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) 269 | { 270 | throw new ParseException("Unsupported operation", p.lineno()); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/assembler/ParseException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.assembler; 19 | 20 | /** 21 | * General purpose parsing exception. 22 | * 23 | * @author Joachim Vandersmissen 24 | */ 25 | public class ParseException extends RuntimeException 26 | { 27 | /** 28 | * Constructs a new ParseException with a message and the line number at 29 | * which the exception occured. The provided line number will be prepended 30 | * to the message. 31 | * 32 | * @param message the message to add to the exception. 33 | * @param line the line number at which the exception occured. 34 | */ 35 | public ParseException(String message, int line) 36 | { 37 | super("Line " + line + ": " + message); 38 | } 39 | 40 | 41 | /** 42 | * Constructs a new ParseException with a message, the line number at which 43 | * the exception occured, and the cause of the exception. The provided line 44 | * number will be prepended to the message. 45 | * 46 | * @param message the message to add to the exception. 47 | * @param line the line number at which the exception occured. 48 | * @param cause the cause of the exception. 49 | */ 50 | public ParseException(String message, int line, Throwable cause) 51 | { 52 | super("Line " + line + ": " + message, cause); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/assembler/io/JbcReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.assembler.io; 19 | 20 | import com.guardsquare.proguard.assembler.ClassParser; 21 | import com.guardsquare.proguard.assembler.Parser; 22 | import proguard.classfile.Clazz; 23 | import proguard.classfile.ProgramClass; 24 | import proguard.classfile.visitor.ClassVisitor; 25 | import proguard.io.DataEntry; 26 | import proguard.io.DataEntryReader; 27 | 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.InputStreamReader; 31 | import java.nio.charset.StandardCharsets; 32 | 33 | /** 34 | * This DataEntryReader parses jbc files using the ClassParser, and then applies 35 | * a given ClassVisitor to the parsed ProgramClass objects. 36 | * 37 | * @author Joachim Vandersmissen 38 | */ 39 | public class JbcReader implements DataEntryReader 40 | { 41 | private final ClassVisitor classVisitor; 42 | 43 | 44 | /** 45 | * Creates a new JbcReader. 46 | * 47 | * @param classVisitor The class visitor to apply after conversion from JBC to Clazz. 48 | */ 49 | public JbcReader(ClassVisitor classVisitor) 50 | { 51 | this.classVisitor = classVisitor; 52 | } 53 | 54 | 55 | // Implementations for DataEntryReader. 56 | 57 | public void read(DataEntry dataEntry) throws IOException 58 | { 59 | try 60 | { 61 | // Get the input stream. 62 | InputStream inputStream = dataEntry.getInputStream(); 63 | 64 | // Wrap it into an input stream reader. 65 | InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); 66 | 67 | // Create a Clazz representation. 68 | Clazz clazz = new ProgramClass(); 69 | clazz.accept(new ClassParser(new Parser(inputStreamReader))); 70 | 71 | // Apply the visitor. 72 | clazz.accept(classVisitor); 73 | 74 | dataEntry.closeInputStream(); 75 | } 76 | catch (Exception e) 77 | { 78 | throw new IOException("An exception occured while assembling " + dataEntry.getName(), e); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/disassembler/AnnotationsPrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.disassembler; 19 | 20 | import com.guardsquare.proguard.assembler.AssemblyConstants; 21 | import proguard.classfile.*; 22 | import proguard.classfile.attribute.CodeAttribute; 23 | import proguard.classfile.attribute.annotation.*; 24 | import proguard.classfile.attribute.annotation.target.*; 25 | import proguard.classfile.attribute.annotation.target.visitor.*; 26 | import proguard.classfile.attribute.annotation.visitor.*; 27 | import proguard.classfile.attribute.visitor.AttributeVisitor; 28 | 29 | /** 30 | * Prints Annotations and TypeAnnotations. 31 | * 32 | * @author Joachim Vandersmissen 33 | */ 34 | public class AnnotationsPrinter 35 | implements AttributeVisitor, 36 | AnnotationVisitor, 37 | TypeAnnotationVisitor, 38 | TargetInfoVisitor, 39 | LocalVariableTargetElementVisitor, 40 | TypePathInfoVisitor, 41 | ElementValueVisitor 42 | { 43 | private final Printer p; 44 | 45 | 46 | /** 47 | * Constructs a new AnnotationsPrinter that uses a Printer. 48 | * 49 | * @param p the Printer to use to print basic structures. 50 | */ 51 | public AnnotationsPrinter(Printer p) 52 | { 53 | this.p = p; 54 | } 55 | 56 | 57 | // Implementations for AttributeVisitor. 58 | 59 | public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute) 60 | { 61 | p.printSpace(); 62 | p.print(AssemblyConstants.BODY_OPEN); 63 | if (annotationsAttribute.u2annotationsCount > 0) 64 | { 65 | p.println(); 66 | p.indent(); 67 | for (int index = 0; index < annotationsAttribute.u2annotationsCount; index++) 68 | { 69 | p.printIndent(); 70 | visitAnnotation(clazz, annotationsAttribute.annotations[index]); 71 | p.println(); 72 | } 73 | 74 | p.outdent(); 75 | p.printIndent(); 76 | } 77 | 78 | p.print(AssemblyConstants.BODY_CLOSE); 79 | } 80 | 81 | 82 | public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute) 83 | { 84 | p.printSpace(); 85 | p.print(AssemblyConstants.BODY_OPEN); 86 | if (parameterAnnotationsAttribute.u1parametersCount > 0) 87 | { 88 | p.println(); 89 | p.indent(); 90 | for (int parameter = 0; parameter < parameterAnnotationsAttribute.u1parametersCount; parameter++) 91 | { 92 | p.printIndent(); 93 | p.print(AssemblyConstants.BODY_OPEN); 94 | if (parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameter] > 0) 95 | { 96 | p.println(); 97 | p.indent(); 98 | for (int index = 0; index < parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameter]; index++) 99 | { 100 | p.printIndent(); 101 | visitAnnotation(clazz, parameterAnnotationsAttribute.parameterAnnotations[parameter][index]); 102 | p.println(); 103 | } 104 | 105 | p.outdent(); 106 | p.printIndent(); 107 | } 108 | 109 | p.print(AssemblyConstants.BODY_CLOSE); 110 | p.println(); 111 | } 112 | 113 | p.outdent(); 114 | p.printIndent(); 115 | } 116 | 117 | p.print(AssemblyConstants.BODY_CLOSE); 118 | } 119 | 120 | 121 | public void visitAnyTypeAnnotationsAttribute(Clazz clazz, TypeAnnotationsAttribute typeAnnotationsAttribute) 122 | { 123 | p.printSpace(); 124 | p.print(AssemblyConstants.BODY_OPEN); 125 | if (typeAnnotationsAttribute.u2annotationsCount > 0) 126 | { 127 | p.println(); 128 | p.indent(); 129 | for (int index = 0; index < typeAnnotationsAttribute.u2annotationsCount; index++) 130 | { 131 | p.printIndent(); 132 | visitTypeAnnotation(clazz, (TypeAnnotation) typeAnnotationsAttribute.annotations[index]); 133 | p.println(); 134 | } 135 | 136 | p.outdent(); 137 | p.printIndent(); 138 | } 139 | 140 | p.print(AssemblyConstants.BODY_CLOSE); 141 | } 142 | 143 | 144 | public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute) 145 | { 146 | p.printSpace(); 147 | annotationDefaultAttribute.defaultValue.accept(clazz, null, this); 148 | } 149 | 150 | 151 | // Implementations for AnnotationVisitor. 152 | 153 | public void visitAnnotation(Clazz clazz, Annotation annotation) 154 | { 155 | p.printType(annotation.getType(clazz)); 156 | p.printSpace(); 157 | p.print(AssemblyConstants.BODY_OPEN); 158 | if (annotation.u2elementValuesCount > 0) 159 | { 160 | p.println(); 161 | p.indent(); 162 | for (int index = 0; index < annotation.u2elementValuesCount; index++) 163 | { 164 | ElementValue elementValue = annotation.elementValues[index]; 165 | p.printIndent(); 166 | p.printWord(elementValue.getMethodName(clazz)); 167 | p.printSpace(); 168 | p.print(AssemblyConstants.EQUALS); 169 | p.printSpace(); 170 | elementValue.accept(clazz, annotation, this); 171 | p.println(); 172 | } 173 | 174 | p.outdent(); 175 | p.printIndent(); 176 | } 177 | 178 | p.print(AssemblyConstants.BODY_CLOSE); 179 | } 180 | 181 | 182 | // Implementations for TypeAnnotationVisitor. 183 | 184 | public void visitTypeAnnotation(Clazz clazz, TypeAnnotation typeAnnotation) 185 | { 186 | p.printType(typeAnnotation.getType(clazz)); 187 | p.printSpace(); 188 | p.print(AssemblyConstants.BODY_OPEN); 189 | if (typeAnnotation.u2elementValuesCount > 0) 190 | { 191 | p.println(); 192 | p.indent(); 193 | for (int index = 0; index < typeAnnotation.u2elementValuesCount; index++) 194 | { 195 | ElementValue elementValue = typeAnnotation.elementValues[index]; 196 | p.printIndent(); 197 | p.printWord(elementValue.getMethodName(clazz)); 198 | p.printSpace(); 199 | p.print(AssemblyConstants.EQUALS); 200 | p.printSpace(); 201 | elementValue.accept(clazz, typeAnnotation, this); 202 | p.println(); 203 | } 204 | 205 | p.outdent(); 206 | p.printIndent(); 207 | } 208 | 209 | p.print(AssemblyConstants.BODY_CLOSE); 210 | p.printSpace(); 211 | printTargetInfoType(typeAnnotation.targetInfo.u1targetType); 212 | typeAnnotation.targetInfoAccept(clazz, this); 213 | p.printSpace(); 214 | p.print(AssemblyConstants.BODY_OPEN); 215 | if (typeAnnotation.typePath.length > 0) 216 | { 217 | p.println(); 218 | p.indent(); 219 | for (int index = 0; index < typeAnnotation.typePath.length; index++) 220 | { 221 | p.printIndent(); 222 | visitTypePathInfo(clazz, typeAnnotation, typeAnnotation.typePath[index]); 223 | p.println(); 224 | } 225 | 226 | p.outdent(); 227 | p.printIndent(); 228 | } 229 | 230 | p.print(AssemblyConstants.BODY_CLOSE); 231 | } 232 | 233 | 234 | // Implementations for TargetInfoVisitor. 235 | 236 | public void visitTypeParameterTargetInfo(Clazz clazz, TypeAnnotation typeAnnotation, TypeParameterTargetInfo typeParameterTargetInfo) 237 | { 238 | p.printSpace(); 239 | p.printNumber(typeParameterTargetInfo.u1typeParameterIndex); 240 | } 241 | 242 | 243 | public void visitSuperTypeTargetInfo(Clazz clazz, TypeAnnotation typeAnnotation, SuperTypeTargetInfo superTypeTargetInfo) 244 | { 245 | p.printSpace(); 246 | p.printNumber(superTypeTargetInfo.u2superTypeIndex); 247 | } 248 | 249 | 250 | public void visitTypeParameterBoundTargetInfo(Clazz clazz, TypeAnnotation typeAnnotation, TypeParameterBoundTargetInfo typeParameterBoundTargetInfo) 251 | { 252 | p.printSpace(); 253 | p.printNumber(typeParameterBoundTargetInfo.u1typeParameterIndex); 254 | p.printSpace(); 255 | p.printNumber(typeParameterBoundTargetInfo.u1boundIndex); 256 | } 257 | 258 | 259 | public void visitEmptyTargetInfo(Clazz clazz, Member member, TypeAnnotation typeAnnotation, EmptyTargetInfo emptyTargetInfo) 260 | { 261 | // NOP. 262 | } 263 | 264 | 265 | public void visitFormalParameterTargetInfo(Clazz clazz, Method method, TypeAnnotation typeAnnotation, FormalParameterTargetInfo formalParameterTargetInfo) 266 | { 267 | p.printSpace(); 268 | p.printNumber(formalParameterTargetInfo.u1formalParameterIndex); 269 | } 270 | 271 | 272 | public void visitThrowsTargetInfo(Clazz clazz, Method method, TypeAnnotation typeAnnotation, ThrowsTargetInfo throwsTargetInfo) 273 | { 274 | p.printSpace(); 275 | p.printNumber(throwsTargetInfo.u2throwsTypeIndex); 276 | } 277 | 278 | 279 | public void visitLocalVariableTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo) 280 | { 281 | p.printSpace(); 282 | p.print(AssemblyConstants.BODY_OPEN); 283 | if (localVariableTargetInfo.u2tableLength > 0) 284 | { 285 | p.println(); 286 | p.indent(); 287 | for (int index = 0; index < localVariableTargetInfo.u2tableLength; index++) 288 | { 289 | p.printIndent(); 290 | visitLocalVariableTargetElement(clazz, method, codeAttribute, typeAnnotation, localVariableTargetInfo, localVariableTargetInfo.table[index]); 291 | p.println(); 292 | } 293 | 294 | p.outdent(); 295 | p.printIndent(); 296 | } 297 | 298 | p.print(AssemblyConstants.BODY_CLOSE); 299 | } 300 | 301 | 302 | public void visitCatchTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, CatchTargetInfo catchTargetInfo) 303 | { 304 | p.printSpace(); 305 | p.printNumber(catchTargetInfo.u2exceptionTableIndex); 306 | } 307 | 308 | 309 | public void visitOffsetTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, OffsetTargetInfo offsetTargetInfo) 310 | { 311 | p.printSpace(); 312 | p.printOffset(offsetTargetInfo.u2offset); 313 | } 314 | 315 | 316 | public void visitTypeArgumentTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, TypeArgumentTargetInfo typeArgumentTargetInfo) 317 | { 318 | p.printSpace(); 319 | p.printOffset(typeArgumentTargetInfo.u2offset); 320 | p.printSpace(); 321 | p.printNumber(typeArgumentTargetInfo.u1typeArgumentIndex); 322 | } 323 | 324 | 325 | // Implementations for TypePathInfoVisitor. 326 | 327 | public void visitTypePathInfo(Clazz clazz, TypeAnnotation typeAnnotation, TypePathInfo typePathInfo) 328 | { 329 | printTypePathKind(typePathInfo.u1typePathKind); 330 | 331 | // Type argument index is optional. 332 | if (typePathInfo.u1typeArgumentIndex != 0) 333 | { 334 | p.printSpace(); 335 | p.printNumber(typePathInfo.u1typeArgumentIndex); 336 | } 337 | 338 | p.print(AssemblyConstants.STATEMENT_END); 339 | } 340 | 341 | 342 | // Implementations for LocalVariableTargetElementVisitor. 343 | 344 | public void visitLocalVariableTargetElement(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo, LocalVariableTargetElement localVariableTargetElement) 345 | { 346 | p.printOffset(localVariableTargetElement.u2startPC); 347 | p.printSpace(); 348 | p.printOffset(localVariableTargetElement.u2startPC + localVariableTargetElement.u2length); 349 | p.printSpace(); 350 | p.printNumber(localVariableTargetElement.u2index); 351 | p.print(AssemblyConstants.STATEMENT_END); 352 | } 353 | 354 | 355 | // Implementations for ElementValueVisitor. 356 | 357 | public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue) 358 | { 359 | clazz.constantPoolEntryAccept(constantElementValue.u2constantValueIndex, new ConstantPrinter(p, constantElementValue.u1tag)); 360 | p.print(AssemblyConstants.STATEMENT_END); 361 | } 362 | 363 | 364 | public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue) 365 | { 366 | p.printType(enumConstantElementValue.getTypeName(clazz)); 367 | p.print(AssemblyConstants.REFERENCE_SEPARATOR); 368 | p.printWord(enumConstantElementValue.getConstantName(clazz)); 369 | p.print(AssemblyConstants.STATEMENT_END); 370 | } 371 | 372 | 373 | public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue) 374 | { 375 | clazz.constantPoolEntryAccept(classElementValue.u2classInfoIndex, new ConstantPrinter(p, false)); 376 | p.print(AssemblyConstants.STATEMENT_END); 377 | } 378 | 379 | 380 | public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue) 381 | { 382 | p.print(ElementValue.TAG_ANNOTATION); 383 | visitAnnotation(clazz, annotationElementValue.annotationValue); 384 | } 385 | 386 | 387 | public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue) 388 | { 389 | p.print(AssemblyConstants.BODY_OPEN); 390 | if (arrayElementValue.u2elementValuesCount > 0) 391 | { 392 | p.println(); 393 | p.indent(); 394 | for (int index = 0; index < arrayElementValue.u2elementValuesCount; index++) 395 | { 396 | p.printIndent(); 397 | arrayElementValue.elementValues[index].accept(clazz, annotation, this); 398 | p.println(); 399 | } 400 | 401 | p.outdent(); 402 | p.printIndent(); 403 | } 404 | 405 | p.print(AssemblyConstants.BODY_CLOSE); 406 | } 407 | 408 | 409 | // Small utility methods. 410 | 411 | /** 412 | * Prints the representation of a TargetInfo type. 413 | * 414 | * @param type the TargetInfo type. 415 | * @throws PrintException if the type is an unknown TargetInfo type. 416 | */ 417 | private void printTargetInfoType(int type) 418 | { 419 | switch (type) 420 | { 421 | case TargetInfo.TARGET_TYPE_PARAMETER_GENERIC_CLASS: p.printWord(AssemblyConstants.TARGET_TYPE_P_A_RAMETER_GENERIC_CLASS); break; 422 | case TargetInfo.TARGET_TYPE_PARAMETER_GENERIC_METHOD: p.printWord(AssemblyConstants.TARGET_TYPE_P_A_RAMETER_GENERIC_METHOD); break; 423 | case TargetInfo.TARGET_TYPE_EXTENDS: p.printWord(AssemblyConstants.TARGET_TYPE_E_X_TENDS); break; 424 | case TargetInfo.TARGET_TYPE_BOUND_GENERIC_CLASS: p.printWord(AssemblyConstants.TARGET_TYPE_B_O_UND_GENERIC_CLASS); break; 425 | case TargetInfo.TARGET_TYPE_BOUND_GENERIC_METHOD: p.printWord(AssemblyConstants.TARGET_TYPE_B_O_UND_GENERIC_METHOD); break; 426 | case TargetInfo.TARGET_TYPE_FIELD: p.printWord(AssemblyConstants.TARGET_TYPE_F_I_ELD); break; 427 | case TargetInfo.TARGET_TYPE_RETURN: p.printWord(AssemblyConstants.TARGET_TYPE_R_E_TURN); break; 428 | case TargetInfo.TARGET_TYPE_RECEIVER: p.printWord(AssemblyConstants.TARGET_TYPE_R_E_CEIVER); break; 429 | case TargetInfo.TARGET_TYPE_PARAMETER: p.printWord(AssemblyConstants.TARGET_TYPE_P_A_RAMETER); break; 430 | case TargetInfo.TARGET_TYPE_THROWS: p.printWord(AssemblyConstants.TARGET_TYPE_T_H_ROWS); break; 431 | case TargetInfo.TARGET_TYPE_LOCAL_VARIABLE: p.printWord(AssemblyConstants.TARGET_TYPE_L_O_CAL_VARIABLE); break; 432 | case TargetInfo.TARGET_TYPE_RESOURCE_VARIABLE: p.printWord(AssemblyConstants.TARGET_TYPE_R_E_SOURCE_VARIABLE); break; 433 | case TargetInfo.TARGET_TYPE_CATCH: p.printWord(AssemblyConstants.TARGET_TYPE_C_A_TCH); break; 434 | case TargetInfo.TARGET_TYPE_INSTANCE_OF: p.printWord(AssemblyConstants.TARGET_TYPE_I_N_STANCE_OF); break; 435 | case TargetInfo.TARGET_TYPE_NEW: p.printWord(AssemblyConstants.TARGET_TYPE_N_E_W); break; 436 | case TargetInfo.TARGET_TYPE_METHOD_REFERENCE_NEW: p.printWord(AssemblyConstants.TARGET_TYPE_M_E_THOD_REFERENCE_NEW); break; 437 | case TargetInfo.TARGET_TYPE_METHOD_REFERENCE: p.printWord(AssemblyConstants.TARGET_TYPE_M_E_THOD_REFERENCE); break; 438 | case TargetInfo.TARGET_TYPE_CAST: p.printWord(AssemblyConstants.TARGET_TYPE_C_A_ST); break; 439 | case TargetInfo.TARGET_TYPE_ARGUMENT_GENERIC_METHODNew: p.printWord(AssemblyConstants.TARGET_TYPE_A_R_GUMENT_GENERIC_METHOD_NEW); break; 440 | case TargetInfo.TARGET_TYPE_ARGUMENT_GENERIC_METHOD: p.printWord(AssemblyConstants.TARGET_TYPE_A_R_GUMENT_GENERIC_METHOD); break; 441 | case TargetInfo.TARGET_TYPE_ARGUMENT_GENERIC_METHODReferenceNew: p.printWord(AssemblyConstants.TARGET_TYPE_A_R_GUMENT_GENERIC_METHOD_REFERENCE_NEW); break; 442 | case TargetInfo.TARGET_TYPE_ARGUMENT_GENERIC_METHODReference: p.printWord(AssemblyConstants.TARGET_TYPE_A_R_GUMENT_GENERIC_METHOD_REFERENCE); break; 443 | default: throw new PrintException("Unknown target info type " + type + "."); 444 | } 445 | } 446 | 447 | 448 | /** 449 | * Prints the representation of a TypePathInfo kind. 450 | * 451 | * @param kind the TypePathInfo kind. 452 | * @throws PrintException if the kind is an unknown TypePathInfo kind. 453 | */ 454 | private void printTypePathKind(int kind) 455 | { 456 | switch (kind) 457 | { 458 | case TypePathInfo.KIND_Array: p.printWord(AssemblyConstants.TYPE_PATH_ARRAY); break; 459 | case TypePathInfo.KIND_Nested: p.printWord(AssemblyConstants.TYPE_PATH_NESTED); break; 460 | case TypePathInfo.KIND_TypeArgumentBound: p.printWord(AssemblyConstants.TYPE_PATH_TYPE_ARGUMENT_BOUND); break; 461 | case TypePathInfo.KIND_TypeArgument: p.printWord(AssemblyConstants.TYPE_PATH_TYPE_ARGUMENT); break; 462 | default: throw new PrintException("Unknown type path kind " + kind + "."); 463 | } 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/disassembler/ClassMembersPrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.disassembler; 19 | 20 | import com.guardsquare.proguard.assembler.AssemblyConstants; 21 | import proguard.classfile.*; 22 | import proguard.classfile.attribute.*; 23 | import proguard.classfile.editor.AttributesEditor; 24 | import proguard.classfile.util.*; 25 | import proguard.classfile.visitor.*; 26 | 27 | import java.util.*; 28 | 29 | /** 30 | * Prints ProgramClass members. 31 | * 32 | * @author Joachim Vandersmissen 33 | */ 34 | public class ClassMembersPrinter 35 | implements ClassVisitor, 36 | MemberVisitor 37 | { 38 | private final Printer p; 39 | 40 | 41 | /** 42 | * Constructs a new ClassMembersPrinter that uses a Printer. 43 | * 44 | * @param p the Printer to use to print basic structures. 45 | */ 46 | public ClassMembersPrinter(Printer p) 47 | { 48 | this.p = p; 49 | } 50 | 51 | 52 | // Implementations for ClassVisitor. 53 | 54 | public void visitAnyClass(Clazz clazz) {} 55 | 56 | 57 | public void visitProgramClass(ProgramClass programClass) 58 | { 59 | if (programClass.u2fieldsCount > 0 || 60 | programClass.u2methodsCount > 0) 61 | { 62 | p.printSpace(); 63 | p.print(AssemblyConstants.BODY_OPEN); 64 | p.println(); 65 | p.indent(); 66 | for (int index = 0; index < programClass.u2fieldsCount; index++) 67 | { 68 | ProgramField field = programClass.fields[index]; 69 | p.printIndent(); 70 | if (printFieldAccessFlags(field.u2accessFlags)) 71 | { 72 | p.printSpace(); 73 | } 74 | 75 | p.printType(field.getDescriptor(programClass)); 76 | p.printSpace(); 77 | p.printWord(field.getName(programClass)); 78 | field.accept(programClass, this); 79 | p.println(); 80 | } 81 | 82 | p.println(); 83 | for (int index = 0; index < programClass.u2methodsCount; index++) 84 | { 85 | ProgramMethod method = programClass.methods[index]; 86 | p.printIndent(); 87 | boolean space = printMethodAccessFlags(method.u2accessFlags); 88 | if (!ClassConstants.METHOD_NAME_CLINIT.equals(method.getName(programClass)) || 89 | !ClassConstants.METHOD_TYPE_CLINIT.equals(method.getDescriptor(programClass))) 90 | { 91 | if (space) 92 | { 93 | p.printSpace(); 94 | } 95 | 96 | p.printMethodReturnType(method.getDescriptor(programClass)); 97 | p.printSpace(); 98 | p.printWord(method.getName(programClass)); 99 | printMethodArguments(programClass, 100 | method.getDescriptor(programClass), 101 | (MethodParametersAttribute) new AttributesEditor(programClass, method, false).findAttribute(Attribute.METHOD_PARAMETERS)); 102 | } 103 | 104 | method.accept(programClass, this); 105 | p.println(); 106 | p.println(); 107 | } 108 | 109 | p.print(AssemblyConstants.BODY_CLOSE); 110 | } 111 | else 112 | { 113 | p.print(AssemblyConstants.STATEMENT_END); 114 | } 115 | } 116 | 117 | 118 | // Implementations for MemberVisitor. 119 | 120 | public void visitProgramField(ProgramClass programClass, ProgramField programField) 121 | { 122 | ConstantValueAttribute constantValueAttribute = 123 | (ConstantValueAttribute) new AttributesEditor(programClass, 124 | programField, 125 | false).findAttribute(Attribute.CONSTANT_VALUE); 126 | if (constantValueAttribute != null) 127 | { 128 | p.printSpace(); 129 | p.print(AssemblyConstants.EQUALS); 130 | p.printSpace(); 131 | programClass.constantPoolEntryAccept(constantValueAttribute.u2constantValueIndex, new ConstantPrinter(p, true)); 132 | } 133 | 134 | programField.accept(programClass, new AttributesPrinter(p)); 135 | p.print(AssemblyConstants.STATEMENT_END); 136 | } 137 | 138 | 139 | public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) 140 | { 141 | ExceptionsAttribute exceptionsAttribute = 142 | (ExceptionsAttribute) new AttributesEditor(programClass, 143 | programMethod, 144 | false).findAttribute(Attribute.EXCEPTIONS); 145 | if (exceptionsAttribute != null && 146 | exceptionsAttribute.u2exceptionIndexTableLength > 0) 147 | { 148 | p.printSpace(); 149 | p.printWord(AssemblyConstants.THROWS); 150 | p.printSpace(); 151 | for (int index = 0; index < exceptionsAttribute.u2exceptionIndexTableLength; index++) 152 | { 153 | p.printWord(ClassUtil.externalClassName(programClass.getClassName(exceptionsAttribute.u2exceptionIndexTable[index]))); 154 | if (index < exceptionsAttribute.u2exceptionIndexTableLength - 1) 155 | { 156 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_SEPARATOR); 157 | p.printSpace(); 158 | } 159 | } 160 | } 161 | 162 | programMethod.accept(programClass, new AttributesPrinter(p)); 163 | if (programMethod.processingInfo != null) 164 | { 165 | // Use shorthand notation. 166 | CodeAttribute codeAttribute = ((CodeAttribute) programMethod.processingInfo); 167 | Map oldLabels = p.labels; 168 | p.labels = new HashMap<>(); 169 | codeAttribute.accept(programClass, programMethod, new LabelsCollector(p.labels)); 170 | codeAttribute.accept(programClass, programMethod, new InstructionsPrinter(p)); 171 | p.labels = oldLabels; 172 | } 173 | else 174 | { 175 | p.print(AssemblyConstants.STATEMENT_END); 176 | } 177 | } 178 | 179 | 180 | // Small utility methods. 181 | 182 | /** 183 | * Prints ProgramMethod arguments. 184 | * 185 | * @param clazz the class being disassembled. 186 | * @param methodDescriptor the internal method descriptor 187 | * containing the method return type and 188 | * the method arguments. 189 | * @param methodParametersAttribute the MethodParametersAttribute to read 190 | * the parameters from. 191 | */ 192 | private void printMethodArguments(Clazz clazz, String methodDescriptor, MethodParametersAttribute methodParametersAttribute) 193 | { 194 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_OPEN); 195 | int index = 0; 196 | InternalTypeEnumeration internalTypeEnumeration = 197 | new InternalTypeEnumeration(methodDescriptor); 198 | while (internalTypeEnumeration.hasMoreTypes()) 199 | { 200 | String type = internalTypeEnumeration.nextType(); 201 | if (methodParametersAttribute != null && 202 | index < methodParametersAttribute.u1parametersCount) 203 | { 204 | if (p.printAccessFlags(methodParametersAttribute.parameters[index].u2accessFlags)) 205 | { 206 | p.printSpace(); 207 | } 208 | } 209 | 210 | p.printType(type); 211 | 212 | if (methodParametersAttribute != null && 213 | index < methodParametersAttribute.u1parametersCount) 214 | { 215 | ParameterInfo parameterInfo = 216 | methodParametersAttribute.parameters[index++]; 217 | // Name index is optional. 218 | if (parameterInfo.u2nameIndex != 0) 219 | { 220 | p.printSpace(); 221 | p.printWord(parameterInfo.getName(clazz)); 222 | } 223 | } 224 | 225 | if (internalTypeEnumeration.hasMoreTypes()) 226 | { 227 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_SEPARATOR); 228 | p.printSpace(); 229 | } 230 | } 231 | 232 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE); 233 | } 234 | 235 | 236 | /** 237 | * Prints ProgramField access flags. 238 | * 239 | * @param accessFlags the access flags. 240 | * @return true if at least one access flag was written, false otherwise. 241 | */ 242 | public boolean printFieldAccessFlags(int accessFlags) 243 | { 244 | if (accessFlags == 0) 245 | { 246 | return false; 247 | } 248 | 249 | StringBuilder stringBuilder = new StringBuilder(); 250 | if ((accessFlags & AccessConstants.PUBLIC) != 0) stringBuilder.append(JavaAccessConstants.PUBLIC).append(' '); 251 | if ((accessFlags & AccessConstants.PRIVATE) != 0) stringBuilder.append(JavaAccessConstants.PRIVATE).append(' '); 252 | if ((accessFlags & AccessConstants.PROTECTED) != 0) stringBuilder.append(JavaAccessConstants.PROTECTED).append(' '); 253 | if ((accessFlags & AccessConstants.STATIC) != 0) stringBuilder.append(JavaAccessConstants.STATIC).append(' '); 254 | if ((accessFlags & AccessConstants.FINAL) != 0) stringBuilder.append(JavaAccessConstants.FINAL).append(' '); 255 | if ((accessFlags & AccessConstants.VOLATILE) != 0) stringBuilder.append(JavaAccessConstants.VOLATILE).append(' '); 256 | if ((accessFlags & AccessConstants.TRANSIENT) != 0) stringBuilder.append(JavaAccessConstants.TRANSIENT).append(' '); 257 | if ((accessFlags & AccessConstants.SYNTHETIC) != 0) stringBuilder.append(JavaAccessConstants.SYNTHETIC).append(' '); 258 | if ((accessFlags & AccessConstants.ENUM) != 0) stringBuilder.append(JavaAccessConstants.ENUM).append(' '); 259 | p.printWord(stringBuilder.toString().trim()); 260 | return true; 261 | } 262 | 263 | 264 | /** 265 | * Prints ProgramMethod access flags. 266 | * 267 | * @param accessFlags the access flags. 268 | * @return true if at least one access flag was written, false otherwise. 269 | */ 270 | public boolean printMethodAccessFlags(int accessFlags) 271 | { 272 | if (accessFlags == 0) 273 | { 274 | return false; 275 | } 276 | 277 | StringBuilder stringBuilder = new StringBuilder(); 278 | if ((accessFlags & AccessConstants.PUBLIC) != 0) stringBuilder.append(JavaAccessConstants.PUBLIC).append(' '); 279 | if ((accessFlags & AccessConstants.PRIVATE) != 0) stringBuilder.append(JavaAccessConstants.PRIVATE).append(' '); 280 | if ((accessFlags & AccessConstants.PROTECTED) != 0) stringBuilder.append(JavaAccessConstants.PROTECTED).append(' '); 281 | if ((accessFlags & AccessConstants.STATIC) != 0) stringBuilder.append(JavaAccessConstants.STATIC).append(' '); 282 | if ((accessFlags & AccessConstants.FINAL) != 0) stringBuilder.append(JavaAccessConstants.FINAL).append(' '); 283 | if ((accessFlags & AccessConstants.SYNCHRONIZED) != 0) stringBuilder.append(JavaAccessConstants.SYNCHRONIZED).append(' '); 284 | if ((accessFlags & AccessConstants.BRIDGE) != 0) stringBuilder.append(JavaAccessConstants.BRIDGE).append(' '); 285 | if ((accessFlags & AccessConstants.VARARGS) != 0) stringBuilder.append(JavaAccessConstants.VARARGS).append(' '); 286 | if ((accessFlags & AccessConstants.NATIVE) != 0) stringBuilder.append(JavaAccessConstants.NATIVE).append(' '); 287 | if ((accessFlags & AccessConstants.ABSTRACT) != 0) stringBuilder.append(JavaAccessConstants.ABSTRACT).append(' '); 288 | if ((accessFlags & AccessConstants.STRICT) != 0) stringBuilder.append(JavaAccessConstants.STRICT).append(' '); 289 | if ((accessFlags & AccessConstants.SYNTHETIC) != 0) stringBuilder.append(JavaAccessConstants.SYNTHETIC).append(' '); 290 | p.printWord(stringBuilder.toString().trim()); 291 | return true; 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/disassembler/ClassPrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.disassembler; 19 | 20 | import com.guardsquare.proguard.assembler.AssemblyConstants; 21 | import proguard.classfile.*; 22 | import proguard.classfile.util.*; 23 | import proguard.classfile.visitor.ClassVisitor; 24 | 25 | /** 26 | * Prints a ProgramClass. 27 | * 28 | * @author Joachim Vandersmissen 29 | */ 30 | public class ClassPrinter 31 | implements ClassVisitor 32 | { 33 | private final Printer p; 34 | 35 | 36 | /** 37 | * Constructs a new ClassPrinter that uses a Printer. 38 | * 39 | * @param p the Printer to use to print basic structures. 40 | */ 41 | public ClassPrinter(Printer p) 42 | { 43 | this.p = p; 44 | } 45 | 46 | 47 | // Implementations for ClassVisitor. 48 | 49 | public void visitAnyClass(Clazz clazz) {} 50 | 51 | 52 | public void visitProgramClass(ProgramClass programClass) 53 | { 54 | p.printWord(AssemblyConstants.VERSION); 55 | p.printSpace(); 56 | p.printWord(ClassUtil.externalClassVersion(programClass.u4version)); 57 | p.print(AssemblyConstants.STATEMENT_END); 58 | p.println(); 59 | if (p.printClassAccessFlags(programClass.u2accessFlags)) 60 | { 61 | p.printSpace(); 62 | } 63 | 64 | p.printWord(ClassUtil.externalClassName(programClass.getName())); 65 | // Syntactic sugar: extends in interfaces define bytecode interfaces. 66 | if ((programClass.u2accessFlags & AccessConstants.INTERFACE) != 0) 67 | { 68 | if (programClass.u2interfacesCount > 0) 69 | { 70 | p.printSpace(); 71 | p.printWord(AssemblyConstants.EXTENDS); 72 | p.printSpace(); 73 | printInterfaces(programClass); 74 | } 75 | } 76 | else 77 | { 78 | if (programClass.u2superClass != 0) 79 | { 80 | p.printSpace(); 81 | p.printWord(AssemblyConstants.EXTENDS); 82 | p.printSpace(); 83 | p.printWord(ClassUtil.externalClassName(programClass.getSuperName())); 84 | } 85 | 86 | if (programClass.u2interfacesCount > 0) 87 | { 88 | p.printSpace(); 89 | p.printWord(AssemblyConstants.IMPLEMENTS); 90 | p.printSpace(); 91 | printInterfaces(programClass); 92 | } 93 | } 94 | 95 | programClass.accept(new AttributesPrinter(p)); 96 | programClass.accept(new ClassMembersPrinter(p)); 97 | 98 | p.flush(); 99 | } 100 | 101 | 102 | // Small utility methods. 103 | 104 | /** 105 | * Prints the interfaces of a class. 106 | * 107 | * @param programClass the class to print the interfaces of. 108 | */ 109 | private void printInterfaces(ProgramClass programClass) 110 | { 111 | for (int index = 0; index < programClass.u2interfacesCount; index++) 112 | { 113 | p.printWord(ClassUtil.externalClassName(programClass.getInterfaceName(index))); 114 | if (index < programClass.u2interfacesCount - 1) 115 | { 116 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_SEPARATOR); 117 | p.printSpace(); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/disassembler/ConstantPrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.disassembler; 19 | 20 | import com.guardsquare.proguard.assembler.AssemblyConstants; 21 | import proguard.classfile.*; 22 | import proguard.classfile.constant.*; 23 | import proguard.classfile.constant.visitor.ConstantVisitor; 24 | import proguard.classfile.util.*; 25 | 26 | /** 27 | * Prints Constants. 28 | * 29 | * @author Joachim Vandersmissen 30 | */ 31 | public class ConstantPrinter 32 | implements ConstantVisitor 33 | { 34 | private static final int INTEGER_FALSE = 0; 35 | private static final int INTEGER_TRUE = 1; 36 | 37 | 38 | private final Printer p; 39 | private final char intType; 40 | private final boolean printFullType; 41 | 42 | 43 | /** 44 | * Constructs a new ClassPrinter that uses a Printer and a boolean flag. 45 | * 46 | * @param p the Printer to use to print basic structures. 47 | * @param printFullType if this is set to true, the full type of a constant 48 | * will be printed if that constant would not be 49 | * unambiguously parsed by the Assembler. 50 | */ 51 | public ConstantPrinter(Printer p, boolean printFullType) 52 | { 53 | this.p = p; 54 | this.intType = 0; 55 | this.printFullType = printFullType; 56 | } 57 | 58 | 59 | /** 60 | * Constructs a new ClassPrinter that uses a Printer, the type of the 61 | * IntegerConstant, and a boolean flag. 62 | * 63 | * @param p the Printer to use to print basic structures. 64 | * @param intType the internal type of the IntegerConstants being visited. 65 | */ 66 | public ConstantPrinter(Printer p, char intType) 67 | { 68 | this.p = p; 69 | this.intType = intType; 70 | this.printFullType = false; 71 | } 72 | 73 | 74 | // Implementations for ConstantVisitor. 75 | 76 | public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) 77 | { 78 | if (intType == TypeConstants.BOOLEAN) 79 | { 80 | if (integerConstant.u4value == INTEGER_TRUE) 81 | { 82 | p.printWord(AssemblyConstants.TRUE); 83 | return; 84 | } 85 | 86 | if (integerConstant.u4value == INTEGER_FALSE) 87 | { 88 | p.printWord(AssemblyConstants.FALSE); 89 | return; 90 | } 91 | } 92 | 93 | if (intType == TypeConstants.CHAR) 94 | { 95 | p.printChar((char) integerConstant.u4value); 96 | return; 97 | } 98 | 99 | if (intType != 0 && intType != TypeConstants.INT) 100 | { 101 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_OPEN); 102 | p.printType(String.valueOf(intType)); 103 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE); 104 | p.printSpace(); 105 | } 106 | 107 | p.printNumber(integerConstant.u4value); 108 | } 109 | 110 | 111 | public void visitLongConstant(Clazz clazz, LongConstant longConstant) 112 | { 113 | p.printNumber(longConstant.u8value); 114 | p.printWord(AssemblyConstants.TYPE_LONG); 115 | } 116 | 117 | 118 | public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant) 119 | { 120 | p.printNumber(floatConstant.f4value); 121 | p.printWord(AssemblyConstants.TYPE_FLOAT); 122 | } 123 | 124 | 125 | public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant) 126 | { 127 | p.printNumber(doubleConstant.f8value); 128 | p.printWord(AssemblyConstants.TYPE_DOUBLE); 129 | } 130 | 131 | 132 | public void visitStringConstant(Clazz clazz, StringConstant stringConstant) 133 | { 134 | p.printString(stringConstant.getString(clazz)); 135 | } 136 | 137 | 138 | public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) 139 | { 140 | p.printString(utf8Constant.getString()); 141 | } 142 | 143 | 144 | public void visitDynamicConstant(Clazz clazz, DynamicConstant dynamicConstant) 145 | { 146 | if (printFullType) 147 | { 148 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_OPEN); 149 | p.printWord(AssemblyConstants.TYPE_DYNAMIC); 150 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE); 151 | p.printSpace(); 152 | } 153 | 154 | p.printNumber(dynamicConstant.u2bootstrapMethodAttributeIndex); 155 | p.printSpace(); 156 | p.printType(dynamicConstant.getType(clazz)); 157 | p.printSpace(); 158 | p.printWord(dynamicConstant.getName(clazz)); 159 | } 160 | 161 | 162 | public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) 163 | { 164 | p.printNumber(invokeDynamicConstant.u2bootstrapMethodAttributeIndex); 165 | p.printSpace(); 166 | p.printMethodReturnType(invokeDynamicConstant.getType(clazz)); 167 | p.printSpace(); 168 | p.printWord(invokeDynamicConstant.getName(clazz)); 169 | p.printMethodArguments(invokeDynamicConstant.getType(clazz)); 170 | } 171 | 172 | 173 | public void visitMethodHandleConstant(Clazz clazz, MethodHandleConstant methodHandleConstant) 174 | { 175 | if (printFullType) 176 | { 177 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_OPEN); 178 | p.printWord(JavaConstants.TYPE_JAVA_LANG_INVOKE_METHODHANDLE); 179 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE); 180 | p.printSpace(); 181 | } 182 | 183 | printReferenceKind(methodHandleConstant.u1referenceKind); 184 | p.printSpace(); 185 | clazz.constantPoolEntryAccept(methodHandleConstant.u2referenceIndex, this); 186 | } 187 | 188 | 189 | public void visitModuleConstant(Clazz clazz, ModuleConstant moduleConstant) 190 | { 191 | throw new PrintException("Unsupported operation"); 192 | } 193 | 194 | 195 | public void visitPackageConstant(Clazz clazz, PackageConstant packageConstant) 196 | { 197 | throw new PrintException("Unsupported operation"); 198 | } 199 | 200 | 201 | public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) 202 | { 203 | if (fieldrefConstant.u2classIndex != ((ProgramClass) clazz).u2thisClass) 204 | { 205 | clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, this); 206 | } 207 | 208 | p.print(AssemblyConstants.REFERENCE_SEPARATOR); 209 | p.printType(fieldrefConstant.getType(clazz)); 210 | p.printSpace(); 211 | p.printWord(fieldrefConstant.getName(clazz)); 212 | } 213 | 214 | 215 | public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) 216 | { 217 | if (anyMethodrefConstant.u2classIndex != ((ProgramClass) clazz).u2thisClass) 218 | { 219 | clazz.constantPoolEntryAccept(anyMethodrefConstant.u2classIndex, this); 220 | } 221 | 222 | p.print(AssemblyConstants.REFERENCE_SEPARATOR); 223 | p.printMethodReturnType(anyMethodrefConstant.getType(clazz)); 224 | p.printSpace(); 225 | p.printWord(anyMethodrefConstant.getName(clazz)); 226 | p.printMethodArguments(anyMethodrefConstant.getType(clazz)); 227 | } 228 | 229 | 230 | public void visitClassConstant(Clazz clazz, ClassConstant classConstant) 231 | { 232 | p.printType(ClassUtil.internalTypeFromClassType(classConstant.getName(clazz))); 233 | } 234 | 235 | 236 | public void visitMethodTypeConstant(Clazz clazz, MethodTypeConstant methodTypeConstant) 237 | { 238 | if (printFullType) 239 | { 240 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_OPEN); 241 | p.printWord(JavaConstants.TYPE_JAVA_LANG_INVOKE_METHODTYPE); 242 | p.print(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE); 243 | p.printSpace(); 244 | } 245 | 246 | p.printMethodReturnType(methodTypeConstant.getType(clazz)); 247 | p.printSpace(); 248 | p.printMethodArguments(methodTypeConstant.getType(clazz)); 249 | } 250 | 251 | 252 | public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) 253 | { 254 | throw new PrintException("Unsupported operation."); 255 | } 256 | 257 | 258 | // Small utility methods. 259 | 260 | /** 261 | * Prints the representation of a reference kind. 262 | * 263 | * @param referenceKind the reference kind. 264 | * @throws PrintException if the reference kind is unknown 265 | */ 266 | private void printReferenceKind(int referenceKind) 267 | { 268 | switch (referenceKind) 269 | { 270 | case MethodHandleConstant.REF_GET_FIELD: p.printWord(AssemblyConstants.REF_GET_FIELD); break; 271 | case MethodHandleConstant.REF_GET_STATIC: p.printWord(AssemblyConstants.REF_GET_STATIC); break; 272 | case MethodHandleConstant.REF_PUT_FIELD: p.printWord(AssemblyConstants.REF_PUT_FIELD); break; 273 | case MethodHandleConstant.REF_PUT_STATIC: p.printWord(AssemblyConstants.REF_PUT_STATIC); break; 274 | case MethodHandleConstant.REF_INVOKE_VIRTUAL: p.printWord(AssemblyConstants.REF_INVOKE_VIRTUAL); break; 275 | case MethodHandleConstant.REF_INVOKE_STATIC: p.printWord(AssemblyConstants.REF_INVOKE_STATIC); break; 276 | case MethodHandleConstant.REF_INVOKE_SPECIAL: p.printWord(AssemblyConstants.REF_INVOKE_SPECIAL); break; 277 | case MethodHandleConstant.REF_NEW_INVOKE_SPECIAL: p.printWord(AssemblyConstants.REF_NEW_INVOKE_SPECIAL); break; 278 | case MethodHandleConstant.REF_INVOKE_INTERFACE: p.printWord(AssemblyConstants.REF_INVOKE_INTERFACE); break; 279 | default: throw new PrintException("Unknown reference kind " + referenceKind + "."); 280 | } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/disassembler/InstructionsPrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.disassembler; 19 | 20 | import com.guardsquare.proguard.assembler.AssemblyConstants; 21 | import proguard.classfile.*; 22 | import proguard.classfile.attribute.*; 23 | import proguard.classfile.attribute.visitor.*; 24 | import proguard.classfile.editor.AttributesEditor; 25 | import proguard.classfile.instruction.*; 26 | import proguard.classfile.instruction.visitor.InstructionVisitor; 27 | import proguard.classfile.util.*; 28 | 29 | import java.util.*; 30 | 31 | /** 32 | * Prints the Instructions, ExceptionInfos and LineNumberInfos of a Code 33 | * attribute. 34 | * 35 | * @author Joachim Vandersmissen 36 | */ 37 | public class InstructionsPrinter 38 | implements AttributeVisitor, 39 | ExceptionInfoVisitor, 40 | LineNumberInfoVisitor, 41 | InstructionVisitor 42 | { 43 | private final Printer p; 44 | 45 | 46 | /** 47 | * Constructs a new InstructionsPrinter that uses a Printer. 48 | * 49 | * @param p the Printer to use to print basic structures. 50 | */ 51 | public InstructionsPrinter(Printer p) 52 | { 53 | this.p = p; 54 | } 55 | 56 | 57 | // Implementations for AttributeVisitor. 58 | 59 | public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) 60 | { 61 | AttributesEditor attributesEditor = 62 | new AttributesEditor((ProgramClass) clazz, 63 | (ProgramMethod) method, 64 | codeAttribute, 65 | false); 66 | 67 | LineNumberTableAttribute lineNumberTableAttribute = 68 | (LineNumberTableAttribute) attributesEditor.findAttribute(Attribute.LINE_NUMBER_TABLE); 69 | LocalVariableTableAttribute localVariableTableAttribute = 70 | (LocalVariableTableAttribute) attributesEditor.findAttribute(Attribute.LOCAL_VARIABLE_TABLE); 71 | LocalVariableTypeTableAttribute localVariableTypeTableAttribute = 72 | (LocalVariableTypeTableAttribute) attributesEditor.findAttribute(Attribute.LOCAL_VARIABLE_TYPE_TABLE); 73 | 74 | p.printSpace(); 75 | p.print(AssemblyConstants.BODY_OPEN); 76 | if (codeAttribute.u4codeLength > 0) 77 | { 78 | p.println(); 79 | p.indent(); 80 | p.indent(); 81 | 82 | int offset = 0; 83 | 84 | // Inclusive because we want LocalVariableInfo and 85 | // LocalVariableTypeInfo lengths to work properly. 86 | while (offset <= codeAttribute.u4codeLength) 87 | { 88 | if (p.labels.containsKey(offset)) 89 | { 90 | p.outdent(); 91 | p.printIndent(); 92 | p.printOffset(offset); 93 | p.print(AssemblyConstants.COLON); 94 | p.println(); 95 | p.indent(); 96 | } 97 | 98 | if (lineNumberTableAttribute != null) 99 | { 100 | List lineNumberInfos = 101 | findAtOffset(lineNumberTableAttribute, offset); 102 | for (int index = 0; index < lineNumberInfos.size(); index++) 103 | { 104 | p.outdent(); 105 | visitLineNumberInfo(clazz, method, codeAttribute, lineNumberInfos.get(index)); 106 | p.indent(); 107 | } 108 | } 109 | 110 | if (localVariableTableAttribute != null) 111 | { 112 | List startingAt = 113 | findStartingAtOffset(localVariableTableAttribute, offset); 114 | for (int index = 0; index < startingAt.size(); index++) 115 | { 116 | LocalVariableInfo localVariableInfo = 117 | startingAt.get(index); 118 | p.outdent(); 119 | p.printIndent(); 120 | p.printWord(AssemblyConstants.LOCAL_VAR_START); 121 | p.printSpace(); 122 | p.printNumber(localVariableInfo.u2index); 123 | p.printSpace(); 124 | p.printType(localVariableInfo.getDescriptor(clazz)); 125 | p.printSpace(); 126 | p.printWord(localVariableInfo.getName(clazz)); 127 | p.println(); 128 | p.indent(); 129 | } 130 | 131 | List endingAt = 132 | findEndingAtOffset(localVariableTableAttribute, offset); 133 | for (int index = 0; index < endingAt.size(); index++) 134 | { 135 | LocalVariableInfo localVariableInfo = 136 | endingAt.get(index); 137 | p.outdent(); 138 | p.printIndent(); 139 | p.printWord(AssemblyConstants.LOCAL_VAR_END); 140 | p.printSpace(); 141 | p.printNumber(localVariableInfo.u2index); 142 | p.println(); 143 | p.indent(); 144 | } 145 | } 146 | 147 | if (localVariableTypeTableAttribute != null) 148 | { 149 | List startingAt = 150 | findStartingAtOffset(localVariableTypeTableAttribute, offset); 151 | for (int index = 0; index < startingAt.size(); index++) 152 | { 153 | LocalVariableTypeInfo localVariableTypeInfo = 154 | startingAt.get(index); 155 | p.outdent(); 156 | p.printIndent(); 157 | p.printWord(AssemblyConstants.LOCAL_VAR_TYPE_START); 158 | p.printSpace(); 159 | p.printNumber(localVariableTypeInfo.u2index); 160 | p.printSpace(); 161 | p.printString(localVariableTypeInfo.getSignature(clazz)); 162 | p.printSpace(); 163 | p.printWord(localVariableTypeInfo.getName(clazz)); 164 | p.println(); 165 | p.indent(); 166 | } 167 | 168 | List endingAt = 169 | findEndingAtOffset(localVariableTypeTableAttribute, offset); 170 | for (int index = 0; index < endingAt.size(); index++) 171 | { 172 | LocalVariableTypeInfo localVariableTypeInfo = 173 | endingAt.get(index); 174 | p.outdent(); 175 | p.printIndent(); 176 | p.printWord(AssemblyConstants.LOCAL_VAR_TYPE_END); 177 | p.printSpace(); 178 | p.printNumber(localVariableTypeInfo.u2index); 179 | p.println(); 180 | p.indent(); 181 | } 182 | } 183 | 184 | List handlerAt = 185 | findHandlerAtOffset(codeAttribute, offset); 186 | for (int index = 0; index < handlerAt.size(); index++) 187 | { 188 | p.outdent(); 189 | p.printIndent(); 190 | p.printWord(AssemblyConstants.CATCH); 191 | visitExceptionInfo(clazz, method, codeAttribute, handlerAt.get(index)); 192 | p.println(); 193 | p.indent(); 194 | } 195 | 196 | if (offset >= codeAttribute.u4codeLength) 197 | { 198 | break; 199 | } 200 | 201 | Instruction instruction = 202 | InstructionFactory.create(codeAttribute.code, offset); 203 | p.printIndent(); 204 | p.printWord(instruction.getName()); 205 | instruction.accept(clazz, method, codeAttribute, offset, this); 206 | offset += instruction.length(offset); 207 | p.println(); 208 | } 209 | 210 | p.outdent(); 211 | p.outdent(); 212 | p.printIndent(); 213 | } 214 | 215 | p.print(AssemblyConstants.BODY_CLOSE); 216 | } 217 | 218 | 219 | // Implementations for InstructionVisitor. 220 | 221 | public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) 222 | { 223 | if (simpleInstruction.opcode == Instruction.OP_BIPUSH || 224 | simpleInstruction.opcode == Instruction.OP_SIPUSH) 225 | { 226 | p.printSpace(); 227 | p.printNumber(simpleInstruction.constant); 228 | } 229 | 230 | if (simpleInstruction.opcode == Instruction.OP_NEWARRAY) 231 | { 232 | p.printSpace(); 233 | p.printType(String.valueOf(InstructionUtil.internalTypeFromArrayType((byte) simpleInstruction.constant))); 234 | } 235 | } 236 | 237 | 238 | public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) 239 | { 240 | if (variableInstruction.wide) 241 | { 242 | p.printWord("_w"); 243 | } 244 | 245 | if (variableInstruction.opcode >= Instruction.OP_ILOAD && 246 | variableInstruction.opcode <= Instruction.OP_ALOAD || 247 | variableInstruction.opcode >= Instruction.OP_ISTORE && 248 | variableInstruction.opcode <= Instruction.OP_ASTORE || 249 | variableInstruction.opcode == Instruction.OP_IINC || 250 | variableInstruction.opcode == Instruction.OP_RET) 251 | { 252 | p.printSpace(); 253 | p.printNumber(variableInstruction.variableIndex); 254 | } 255 | 256 | if (variableInstruction.opcode == Instruction.OP_IINC) 257 | { 258 | p.printSpace(); 259 | p.printNumber(variableInstruction.constant); 260 | } 261 | } 262 | 263 | 264 | public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) 265 | { 266 | p.printSpace(); 267 | clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantPrinter(p, true)); 268 | if (constantInstruction.opcode == Instruction.OP_MULTIANEWARRAY) 269 | { 270 | p.printSpace(); 271 | p.printNumber(constantInstruction.constant); 272 | } 273 | } 274 | 275 | 276 | public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) 277 | { 278 | p.printSpace(); 279 | p.printOffset(offset + branchInstruction.branchOffset); 280 | } 281 | 282 | 283 | public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction) 284 | { 285 | p.printSpace(); 286 | p.print(AssemblyConstants.BODY_OPEN); 287 | p.println(); 288 | p.indent(); 289 | for (int index = 0; index < tableSwitchInstruction.highCase - tableSwitchInstruction.lowCase + 1; index++) 290 | { 291 | p.printIndent(); 292 | p.printWord(AssemblyConstants.CASE); 293 | p.printSpace(); 294 | p.printNumber(tableSwitchInstruction.lowCase + index); 295 | p.print(AssemblyConstants.COLON); 296 | p.printSpace(); 297 | p.printOffset(offset + tableSwitchInstruction.jumpOffsets[index]); 298 | p.println(); 299 | } 300 | 301 | p.printIndent(); 302 | p.printWord(AssemblyConstants.DEFAULT); 303 | p.print(AssemblyConstants.COLON); 304 | p.printSpace(); 305 | p.printOffset(offset + tableSwitchInstruction.defaultOffset); 306 | p.println(); 307 | p.outdent(); 308 | p.printIndent(); 309 | p.print(AssemblyConstants.BODY_CLOSE); 310 | } 311 | 312 | 313 | public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction) 314 | { 315 | p.printSpace(); 316 | p.print(AssemblyConstants.BODY_OPEN); 317 | p.println(); 318 | p.indent(); 319 | for (int index = 0; index < lookUpSwitchInstruction.cases.length; index++) 320 | { 321 | p.printIndent(); 322 | p.printWord(AssemblyConstants.CASE); 323 | p.printSpace(); 324 | p.printNumber(lookUpSwitchInstruction.cases[index]); 325 | p.print(AssemblyConstants.COLON); 326 | p.printSpace(); 327 | p.printOffset(offset + lookUpSwitchInstruction.jumpOffsets[index]); 328 | p.println(); 329 | } 330 | 331 | p.printIndent(); 332 | p.printWord(AssemblyConstants.DEFAULT); 333 | p.print(AssemblyConstants.COLON); 334 | p.printSpace(); 335 | p.printOffset(offset + lookUpSwitchInstruction.defaultOffset); 336 | p.println(); 337 | p.outdent(); 338 | p.printIndent(); 339 | p.print(AssemblyConstants.BODY_CLOSE); 340 | } 341 | 342 | 343 | // Implementations for ExceptionInfoVisitor. 344 | 345 | public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) 346 | { 347 | p.printSpace(); 348 | 349 | // If u2catchType = 0, the catch block is a finally block. 350 | if (exceptionInfo.u2catchType == 0) 351 | { 352 | p.printWord(AssemblyConstants.ANY); 353 | } 354 | else 355 | { 356 | p.printType(ClassUtil.internalTypeFromClassType(clazz.getClassName(exceptionInfo.u2catchType))); 357 | } 358 | 359 | p.printSpace(); 360 | p.printOffset(exceptionInfo.u2startPC); 361 | p.printSpace(); 362 | p.printOffset(exceptionInfo.u2endPC); 363 | } 364 | 365 | 366 | // Implementations for LineNumberInfoVisitor. 367 | 368 | public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo) 369 | { 370 | p.printIndent(); 371 | p.printWord(AssemblyConstants.LINE); 372 | p.printSpace(); 373 | p.printNumber(lineNumberInfo.u2lineNumber); 374 | p.println(); 375 | } 376 | 377 | 378 | // Small utility methods. 379 | 380 | /** 381 | * Returns all LineNumberInfos at an offset in the bytecode. 382 | * 383 | * @param lineNumberTableAttribute the LineNumberTableAttribute to search in. 384 | * @param offset the bytecode offset to search for. 385 | * @return the LineNumberInfos at the offset. 386 | */ 387 | private List findAtOffset(LineNumberTableAttribute lineNumberTableAttribute, int offset) 388 | { 389 | List lineNumberInfos = 390 | new ArrayList<>(lineNumberTableAttribute.u2lineNumberTableLength); 391 | for (int index = 0; index < lineNumberTableAttribute.u2lineNumberTableLength; index++) 392 | { 393 | LineNumberInfo lineNumberInfo = 394 | lineNumberTableAttribute.lineNumberTable[index]; 395 | if (lineNumberInfo.u2startPC == offset) 396 | { 397 | lineNumberInfos.add(lineNumberInfo); 398 | } 399 | } 400 | 401 | return lineNumberInfos; 402 | } 403 | 404 | 405 | /** 406 | * Returns the ExceptionInfos whose handler are at an offset in the bytecode. 407 | * 408 | * @param codeAttribute the code attribute to search in. 409 | * @param offset the bytecode offset to search for. 410 | * @return the ExceptionInfos whose handler are at the offset. 411 | */ 412 | private List findHandlerAtOffset(CodeAttribute codeAttribute, int offset) 413 | { 414 | List handlerAt = 415 | new ArrayList<>(codeAttribute.u2exceptionTableLength); 416 | for (int index = 0; index < codeAttribute.u2exceptionTableLength; index++) 417 | { 418 | ExceptionInfo exceptionInfo = 419 | codeAttribute.exceptionTable[index]; 420 | if (exceptionInfo.u2handlerPC == offset) 421 | { 422 | handlerAt.add(exceptionInfo); 423 | } 424 | } 425 | 426 | return handlerAt; 427 | } 428 | 429 | 430 | /** 431 | * Returns all LocalVariableInfos starting at an offset in the bytecode. 432 | * 433 | * @param localVariableTableAttribute the LocalVariableTableAttribute to 434 | * search in. 435 | * @param offset the bytecode offset to search for. 436 | * @return the LocalVariableInfos starting at the offset. 437 | */ 438 | private List findStartingAtOffset(LocalVariableTableAttribute localVariableTableAttribute, int offset) 439 | { 440 | List startingAt = 441 | new ArrayList<>(localVariableTableAttribute.u2localVariableTableLength); 442 | for (int index = 0; index < localVariableTableAttribute.u2localVariableTableLength; index++) 443 | { 444 | LocalVariableInfo localVariableInfo = 445 | localVariableTableAttribute.localVariableTable[index]; 446 | if (localVariableInfo.u2startPC == offset) 447 | { 448 | startingAt.add(localVariableInfo); 449 | } 450 | } 451 | 452 | return startingAt; 453 | } 454 | 455 | 456 | /** 457 | * Returns all LocalVariableInfos ending at an offset in the bytecode. 458 | * 459 | * @param localVariableTableAttribute the LocalVariableTableAttribute to 460 | * search in. 461 | * @param offset the bytecode offset to search for. 462 | * @return the LocalVariableInfos ending at the offset. 463 | */ 464 | private List findEndingAtOffset(LocalVariableTableAttribute localVariableTableAttribute, int offset) 465 | { 466 | List endingAt = 467 | new ArrayList<>(localVariableTableAttribute.u2localVariableTableLength); 468 | for (int index = 0; index < localVariableTableAttribute.u2localVariableTableLength; index++) 469 | { 470 | LocalVariableInfo localVariableInfo = 471 | localVariableTableAttribute.localVariableTable[index]; 472 | if (localVariableInfo.u2startPC + localVariableInfo.u2length == offset) 473 | { 474 | endingAt.add(localVariableInfo); 475 | } 476 | } 477 | 478 | return endingAt; 479 | } 480 | 481 | 482 | /** 483 | * Returns all LocalVariableTypeInfos starting at an offset in the bytecode. 484 | * 485 | * @param localVariableTypeTableAttribute the LocalVariableTypeTableAttribute 486 | * to search in. 487 | * @param offset the bytecode offset to search for. 488 | * @return the LocalVariableTypeInfos starting at the offset. 489 | */ 490 | private List findStartingAtOffset(LocalVariableTypeTableAttribute localVariableTypeTableAttribute, int offset) 491 | { 492 | List startingAt = 493 | new ArrayList<>(localVariableTypeTableAttribute.u2localVariableTypeTableLength); 494 | for (int index = 0; index < localVariableTypeTableAttribute.u2localVariableTypeTableLength; index++) 495 | { 496 | LocalVariableTypeInfo localVariableTypeInfo = 497 | localVariableTypeTableAttribute.localVariableTypeTable[index]; 498 | if (localVariableTypeInfo.u2startPC == offset) 499 | { 500 | startingAt.add(localVariableTypeInfo); 501 | } 502 | } 503 | 504 | return startingAt; 505 | } 506 | 507 | 508 | /** 509 | * Returns all LocalVariableTypeInfos ending at an offset in the bytecode. 510 | * 511 | * @param localVariableTypeTableAttribute the LocalVariableTypeTableAttribute 512 | * to search in. 513 | * @param offset the bytecode offset to search for. 514 | * @return the LocalVariableTypeInfos ending at the offset. 515 | */ 516 | private List findEndingAtOffset(LocalVariableTypeTableAttribute localVariableTypeTableAttribute, int offset) 517 | { 518 | List endingAt = 519 | new ArrayList<>(localVariableTypeTableAttribute.u2localVariableTypeTableLength); 520 | for (int index = 0; index < localVariableTypeTableAttribute.u2localVariableTypeTableLength; index++) 521 | { 522 | LocalVariableTypeInfo localVariableTypeInfo = 523 | localVariableTypeTableAttribute.localVariableTypeTable[index]; 524 | if (localVariableTypeInfo.u2startPC + localVariableTypeInfo.u2length == offset) 525 | { 526 | endingAt.add(localVariableTypeInfo); 527 | } 528 | } 529 | 530 | return endingAt; 531 | } 532 | } 533 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/disassembler/LabelsCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.disassembler; 19 | 20 | import com.guardsquare.proguard.assembler.AssemblyConstants; 21 | import proguard.classfile.*; 22 | import proguard.classfile.attribute.*; 23 | import proguard.classfile.attribute.annotation.*; 24 | import proguard.classfile.attribute.annotation.target.*; 25 | import proguard.classfile.attribute.annotation.target.visitor.*; 26 | import proguard.classfile.attribute.annotation.visitor.TypeAnnotationVisitor; 27 | import proguard.classfile.attribute.preverification.*; 28 | import proguard.classfile.attribute.visitor.*; 29 | import proguard.classfile.instruction.*; 30 | import proguard.classfile.instruction.visitor.InstructionVisitor; 31 | 32 | import java.util.Map; 33 | 34 | /** 35 | * Visits relevant offsets in a CodeAttribute and transforms them to labels in 36 | * a map. 37 | * 38 | * @author Joachim Vandersmissen 39 | */ 40 | public class LabelsCollector 41 | implements AttributeVisitor, 42 | ExceptionInfoVisitor, 43 | TypeAnnotationVisitor, 44 | TargetInfoVisitor, 45 | LocalVariableTargetElementVisitor, 46 | InstructionVisitor 47 | { 48 | private final Map labels; 49 | 50 | 51 | /** 52 | * Constructs a new LabelsCollector. 53 | * 54 | * @param labels the map to collect the labels in. 55 | */ 56 | public LabelsCollector(Map labels) 57 | { 58 | this.labels = labels; 59 | } 60 | 61 | 62 | // Implementations for AttributeVisitor. 63 | 64 | public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} 65 | 66 | 67 | public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) 68 | { 69 | codeAttribute.instructionsAccept(clazz, method, this); 70 | codeAttribute.exceptionsAccept(clazz, method, this); 71 | codeAttribute.attributesAccept(clazz, method, this); 72 | } 73 | 74 | 75 | public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute) 76 | { 77 | // For now we don't look at StackMap attributes, as they aren't written 78 | // to the output anyway. 79 | } 80 | 81 | 82 | public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute) 83 | { 84 | // For now we don't look at StackMapTable attributes, as they aren't written 85 | // to the output anyway. 86 | } 87 | 88 | 89 | public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute) 90 | { 91 | // Line numbers are handled inline. 92 | } 93 | 94 | 95 | public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute) 96 | { 97 | // Local variables are handled inline. 98 | } 99 | 100 | 101 | public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute) 102 | { 103 | // Local variable types are handled inline. 104 | } 105 | 106 | 107 | public void visitAnyTypeAnnotationsAttribute(Clazz clazz, TypeAnnotationsAttribute typeAnnotationsAttribute) 108 | { 109 | typeAnnotationsAttribute.typeAnnotationsAccept(clazz, this); 110 | } 111 | 112 | 113 | // Implementations for InstructionsVisitor. 114 | 115 | public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} 116 | 117 | 118 | public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction) 119 | { 120 | collectOffset(offset + branchInstruction.branchOffset); 121 | } 122 | 123 | 124 | public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction) 125 | { 126 | collectOffset(offset + switchInstruction.defaultOffset); 127 | for (int index = 0; index < switchInstruction.jumpOffsets.length; index++) 128 | { 129 | collectOffset(offset + switchInstruction.jumpOffsets[index]); 130 | } 131 | } 132 | 133 | 134 | // Implementations for TypeAnnotationVisitor. 135 | 136 | public void visitTypeAnnotation(Clazz clazz, TypeAnnotation typeAnnotation) 137 | { 138 | typeAnnotation.targetInfoAccept(clazz, this); 139 | } 140 | 141 | 142 | // Implementations for TargetInfoVisitor. 143 | 144 | public void visitAnyTargetInfo(Clazz clazz, TypeAnnotation typeAnnotation, TargetInfo targetInfo) {} 145 | 146 | 147 | public void visitLocalVariableTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo) 148 | { 149 | localVariableTargetInfo.targetElementsAccept(clazz, method, codeAttribute, typeAnnotation, this); 150 | } 151 | 152 | 153 | public void visitOffsetTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, OffsetTargetInfo offsetTargetInfo) 154 | { 155 | collectOffset(offsetTargetInfo.u2offset); 156 | } 157 | 158 | 159 | public void visitTypeArgumentTargetInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, TypeArgumentTargetInfo typeArgumentTargetInfo) 160 | { 161 | collectOffset(typeArgumentTargetInfo.u2offset); 162 | } 163 | 164 | 165 | // Implementations for LocalVariableTargetElementVisitor. 166 | 167 | public void visitLocalVariableTargetElement(Clazz clazz, Method method, CodeAttribute codeAttribute, TypeAnnotation typeAnnotation, LocalVariableTargetInfo localVariableTargetInfo, LocalVariableTargetElement localVariableTargetElement) 168 | { 169 | collectOffset(localVariableTargetElement.u2startPC); 170 | collectOffset(localVariableTargetElement.u2startPC + localVariableTargetElement.u2length); 171 | } 172 | 173 | 174 | // Implementations for ExceptionInfoVisitor. 175 | 176 | public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) 177 | { 178 | collectOffset(exceptionInfo.u2startPC); 179 | collectOffset(exceptionInfo.u2endPC); 180 | // Exception handlers are handled inline. 181 | } 182 | 183 | 184 | // Small utility methods. 185 | 186 | /** 187 | * Adds an offset to the labels map. 188 | * 189 | * @param offset the offset to add. 190 | */ 191 | private void collectOffset(int offset) 192 | { 193 | if (!this.labels.containsKey(offset)) 194 | { 195 | this.labels.put(offset, AssemblyConstants.LABEL + (labels.size() + 1)); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/disassembler/PrintException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.disassembler; 19 | 20 | /** 21 | * General purpose printing exception. 22 | * 23 | * @author Joachim Vandersmissen 24 | */ 25 | public class PrintException extends RuntimeException 26 | { 27 | /** 28 | * Constructs a new PrintException with a message. 29 | * 30 | * @param message the message to add to the exception. 31 | */ 32 | public PrintException(String message) 33 | { 34 | super(message); 35 | } 36 | 37 | 38 | /** 39 | * Constructs a new PrintException with a message and the cause of the 40 | * exception. 41 | * 42 | * @param message the message to add to the exception. 43 | * @param cause the cause of the exception. 44 | */ 45 | public PrintException(String message, Throwable cause) 46 | { 47 | super(message, cause); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/disassembler/Printer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.disassembler; 19 | 20 | import com.guardsquare.proguard.assembler.AssemblyConstants; 21 | import proguard.classfile.*; 22 | import proguard.classfile.util.ClassUtil; 23 | 24 | import java.io.*; 25 | import java.util.Map; 26 | 27 | /** 28 | * General purpose printer. 29 | * 30 | * @author Joachim Vandersmissen 31 | */ 32 | public class Printer 33 | { 34 | private static final String INDENTATION = " "; 35 | 36 | private final Writer w; 37 | 38 | private int indentation; 39 | 40 | public Map labels; 41 | 42 | 43 | /** 44 | * Creates a new Printer that writes to the given writer. 45 | * 46 | * @param writer the writer to write to. 47 | */ 48 | public Printer(Writer writer) 49 | { 50 | this.w = writer; 51 | } 52 | 53 | 54 | /** 55 | * Increases the level of indentation by one. 56 | */ 57 | public void indent() 58 | { 59 | indentation++; 60 | } 61 | 62 | 63 | /** 64 | * Decreases the level of indentation by one. 65 | */ 66 | public void outdent() 67 | { 68 | indentation--; 69 | } 70 | 71 | 72 | /** 73 | * Writes a character to the underlying writer. 74 | * 75 | * @param c the character to write. 76 | * @throws PrintException if an IOException occured while writing. 77 | */ 78 | private void write(int c) 79 | { 80 | try 81 | { 82 | w.write(c); 83 | } 84 | catch (IOException e) 85 | { 86 | throw new PrintException("An IOException occured while writing.", e); 87 | } 88 | } 89 | 90 | 91 | /** 92 | * Writes a string to the underlying writer. 93 | * 94 | * @param s the string to write. 95 | * @throws PrintException if an IOException occured while writing. 96 | */ 97 | private void write(String s) 98 | { 99 | try 100 | { 101 | w.write(s); 102 | } 103 | catch (IOException e) 104 | { 105 | throw new PrintException("An IOException occured while writing.", e); 106 | } 107 | } 108 | 109 | 110 | /** 111 | * Writes the system line separator. 112 | */ 113 | public void println() 114 | { 115 | write(System.lineSeparator()); 116 | } 117 | 118 | 119 | /** 120 | * Writes the indentation. 121 | */ 122 | public void printIndent() 123 | { 124 | for (int index = 0; index < indentation; index++) 125 | { 126 | write(INDENTATION); 127 | } 128 | } 129 | 130 | 131 | /** 132 | * Writes a single character. 133 | * 134 | * @param c The character to write. 135 | */ 136 | public void print(char c) 137 | { 138 | write(c); 139 | } 140 | 141 | 142 | /** 143 | * Writes a single space. 144 | */ 145 | public void printSpace() 146 | { 147 | write(' '); 148 | } 149 | 150 | 151 | /** 152 | * Writes a number. 153 | * The number will be formatted according to the DecimalFormat field. 154 | * 155 | * @param number the number to write. 156 | */ 157 | public void printNumber(double number) 158 | { 159 | write(AssemblyConstants.DOUBLE_TO_STRING.format(number)); 160 | } 161 | 162 | 163 | /** 164 | * Writes a word. 165 | * 166 | * @param word the word to write. 167 | */ 168 | public void printWord(String word) 169 | { 170 | write(word); 171 | } 172 | 173 | 174 | /** 175 | * Writes a string, surrounded by double quotes. 176 | * 177 | * @param string the string value. 178 | */ 179 | public void printString(String string) 180 | { 181 | write(AssemblyConstants.STRING_QUOTE); 182 | StringBuilder stringBuilder = new StringBuilder(); 183 | for (char c : string.toCharArray()) 184 | { 185 | if (c < ' ' || c > '~') 186 | { 187 | stringBuilder.append("\\" + Integer.toOctalString(c)); 188 | } 189 | else if (c == '"') 190 | { 191 | stringBuilder.append("\\\""); 192 | } 193 | else if (c == '\\') 194 | { 195 | stringBuilder.append("\\\\"); 196 | } 197 | else 198 | { 199 | stringBuilder.append(c); 200 | } 201 | } 202 | 203 | write(stringBuilder.toString()); 204 | write(AssemblyConstants.STRING_QUOTE); 205 | } 206 | 207 | 208 | /** 209 | * Writes a char, surrounded by single quotes. 210 | * 211 | * @param c the char value. 212 | */ 213 | public void printChar(char c) 214 | { 215 | write(AssemblyConstants.CHAR_QUOTE); 216 | if (c < ' ' || c > '~') 217 | { 218 | write("\\" + Integer.toOctalString(c)); 219 | } 220 | else if (c == '\'') 221 | { 222 | write("\\'"); 223 | } 224 | else if (c == '\\') 225 | { 226 | write("\\\\"); 227 | } 228 | else 229 | { 230 | write(c); 231 | } 232 | 233 | write(AssemblyConstants.CHAR_QUOTE); 234 | } 235 | 236 | 237 | /** 238 | * Writes an internal field type. 239 | * 240 | * @param type the internal field type. 241 | */ 242 | public void printType(String type) 243 | { 244 | printWord(ClassUtil.externalType(type)); 245 | } 246 | 247 | 248 | /** 249 | * Writes a method return type. 250 | * 251 | * @param methodDescriptor The internal method descriptor containing the 252 | * method return type and the method arguments. 253 | */ 254 | public void printMethodReturnType(String methodDescriptor) 255 | { 256 | printType(ClassUtil.internalMethodReturnType(methodDescriptor)); 257 | } 258 | 259 | 260 | /** 261 | * Writes method arguments. 262 | * 263 | * @param methodDescriptor The internal method descriptor containing the 264 | * method return type and the method arguments. 265 | */ 266 | public void printMethodArguments(String methodDescriptor) 267 | { 268 | print(JavaTypeConstants.METHOD_ARGUMENTS_OPEN); 269 | printWord(ClassUtil.externalMethodArguments(methodDescriptor)); 270 | print(JavaTypeConstants.METHOD_ARGUMENTS_CLOSE); 271 | } 272 | 273 | 274 | /** 275 | * Writes MethodParameters, RequiresInfo, ExportsInfo, and OpensInfo access 276 | * flags. 277 | * 278 | * @param accessFlags the access flags. 279 | * @return true if at least one access flag was written, false otherwise. 280 | */ 281 | public boolean printAccessFlags(int accessFlags) 282 | { 283 | if (accessFlags == 0) 284 | { 285 | return false; 286 | } 287 | 288 | StringBuilder stringBuilder = new StringBuilder(); 289 | if ((accessFlags & AccessConstants.FINAL) != 0) stringBuilder.append(JavaAccessConstants.FINAL).append(' '); 290 | if ((accessFlags & AccessConstants.TRANSITIVE) != 0) stringBuilder.append(JavaAccessConstants.TRANSITIVE).append(' '); 291 | if ((accessFlags & AccessConstants.STATIC_PHASE) != 0) stringBuilder.append(JavaAccessConstants.STATIC_PHASE).append(' '); 292 | if ((accessFlags & AccessConstants.SYNTHETIC) != 0) stringBuilder.append(JavaAccessConstants.SYNTHETIC).append(' '); 293 | if ((accessFlags & AccessConstants.MANDATED) != 0) stringBuilder.append(JavaAccessConstants.MANDATED).append(' '); 294 | printWord(stringBuilder.toString().trim()); 295 | return true; 296 | } 297 | 298 | 299 | /** 300 | * Writes ProgramClass and InnerClassInfo access flags and class types. 301 | * 302 | * @param accessFlags the access flags. 303 | * @return true if at least one access flag or class type was written, false 304 | * otherwise. 305 | */ 306 | public boolean printClassAccessFlags(int accessFlags) 307 | { 308 | StringBuilder stringBuilder = new StringBuilder(); 309 | if ((accessFlags & AccessConstants.PUBLIC) != 0) stringBuilder.append(JavaAccessConstants.PUBLIC).append(' '); 310 | if ((accessFlags & AccessConstants.PRIVATE) != 0) stringBuilder.append(JavaAccessConstants.PRIVATE).append(' '); 311 | if ((accessFlags & AccessConstants.PROTECTED) != 0) stringBuilder.append(JavaAccessConstants.PROTECTED).append(' '); 312 | if ((accessFlags & AccessConstants.STATIC) != 0) stringBuilder.append(JavaAccessConstants.STATIC).append(' '); 313 | if ((accessFlags & AccessConstants.FINAL) != 0) stringBuilder.append(JavaAccessConstants.FINAL).append(' '); 314 | if ((accessFlags & AccessConstants.ABSTRACT) != 0) stringBuilder.append(JavaAccessConstants.ABSTRACT).append(' '); 315 | if ((accessFlags & AccessConstants.SYNTHETIC) != 0) stringBuilder.append(JavaAccessConstants.SYNTHETIC).append(' '); 316 | 317 | if ((accessFlags & AccessConstants.MODULE) != 0) 318 | { 319 | stringBuilder.append(JavaAccessConstants.MODULE); 320 | printWord(stringBuilder.toString()); 321 | return true; 322 | } 323 | 324 | if ((accessFlags & AccessConstants.ENUM) != 0) 325 | { 326 | stringBuilder.append(JavaAccessConstants.ENUM); 327 | printWord(stringBuilder.toString()); 328 | return true; 329 | } 330 | 331 | if ((accessFlags & AccessConstants.ANNOTATION) != 0) 332 | { 333 | stringBuilder.append(JavaAccessConstants.ANNOTATION).append(JavaAccessConstants.INTERFACE); 334 | printWord(stringBuilder.toString()); 335 | return true; 336 | } 337 | 338 | if ((accessFlags & AccessConstants.INTERFACE) != 0) 339 | { 340 | stringBuilder.append(JavaAccessConstants.INTERFACE); 341 | printWord(stringBuilder.toString()); 342 | return true; 343 | } 344 | 345 | stringBuilder.append(AssemblyConstants.CLASS); 346 | printWord(stringBuilder.toString()); 347 | return true; 348 | } 349 | 350 | 351 | /** 352 | * Writes a bytecode offset. 353 | * 354 | * @param offset the bytecode offset. 355 | * @throws PrintException if the offset was not found in the labels map. 356 | */ 357 | public void printOffset(int offset) 358 | { 359 | if (!labels.containsKey(offset)) 360 | { 361 | throw new PrintException("Offset " + offset + " not found in labels."); 362 | } 363 | 364 | printWord(labels.get(offset)); 365 | } 366 | 367 | 368 | /** 369 | * Flushes the writer. 370 | */ 371 | public void flush() 372 | { 373 | try 374 | { 375 | w.flush(); 376 | } 377 | catch (IOException ignore) {} 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /pga-lib/src/main/java/com/guardsquare/proguard/disassembler/io/JbcDataEntryWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ProGuard assembler/disassembler for Java bytecode. 3 | * 4 | * Copyright (c) 2019-2020 Guardsquare NV 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package com.guardsquare.proguard.disassembler.io; 19 | 20 | import com.guardsquare.proguard.assembler.AssemblyConstants; 21 | import com.guardsquare.proguard.disassembler.ClassPrinter; 22 | import com.guardsquare.proguard.disassembler.Printer; 23 | import proguard.classfile.ClassPool; 24 | import proguard.classfile.Clazz; 25 | import proguard.io.DataEntry; 26 | import proguard.io.DataEntryWriter; 27 | 28 | import java.io.FilterOutputStream; 29 | import java.io.IOException; 30 | import java.io.OutputStream; 31 | import java.io.OutputStreamWriter; 32 | import java.io.PrintWriter; 33 | import java.nio.charset.StandardCharsets; 34 | 35 | /** 36 | * This DataEntryWriter finds received class entries in the given class pool 37 | * and disassembles them as jbc files to the given data entry writer. For 38 | * resource entries, it returns valid output streams. For jbc entries, it 39 | * returns output streams that must not be used. 40 | * 41 | * @author Joachim Vandersmissen 42 | */ 43 | public class JbcDataEntryWriter 44 | implements DataEntryWriter 45 | { 46 | private final ClassPool classPool; 47 | private final DataEntryWriter dataEntryWriter; 48 | 49 | 50 | /** 51 | * Creates a new JbcDataEntryWriter. 52 | * @param classPool the class pool in which classes are found. 53 | * @param dataEntryWriter the writer to which the jbc file is disassembled. 54 | */ 55 | public JbcDataEntryWriter(ClassPool classPool, 56 | DataEntryWriter dataEntryWriter) 57 | { 58 | this.classPool = classPool; 59 | this.dataEntryWriter = dataEntryWriter; 60 | } 61 | 62 | 63 | // Implementations for DataEntryWriter. 64 | 65 | public boolean createDirectory(DataEntry dataEntry) throws IOException 66 | { 67 | return dataEntryWriter.createDirectory(dataEntry); 68 | } 69 | 70 | 71 | public boolean sameOutputStream(DataEntry dataEntry1, 72 | DataEntry dataEntry2) 73 | throws IOException 74 | { 75 | return dataEntryWriter.sameOutputStream(dataEntry1, dataEntry2); 76 | } 77 | 78 | 79 | public OutputStream createOutputStream(DataEntry dataEntry) throws IOException 80 | { 81 | // Is it a class entry? 82 | String name = dataEntry.getName(); 83 | if (name.endsWith(AssemblyConstants.JBC_EXTENSION)) 84 | { 85 | // Does it still have a corresponding class? 86 | String className = name.substring(0, name.length() - AssemblyConstants.JBC_EXTENSION.length()); 87 | Clazz clazz = classPool.getClass(className); 88 | if (clazz != null) 89 | { 90 | // Get the output stream for this input entry. 91 | OutputStream outputStream = dataEntryWriter.createOutputStream(dataEntry); 92 | if (outputStream != null) 93 | { 94 | // Disassemble the class to the output stream. 95 | try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) 96 | { 97 | clazz.accept(new ClassPrinter(new Printer(outputStreamWriter))); 98 | } 99 | catch (Exception e) 100 | { 101 | throw new IOException("An exception occured while disassembling " + dataEntry.getName(), e); 102 | } 103 | } 104 | } 105 | 106 | // Return a dummy, non-null output stream (to work with cascading 107 | // output writers). 108 | return new FilterOutputStream(null); 109 | } 110 | 111 | // Delegate for resource entries. 112 | return dataEntryWriter.createOutputStream(dataEntry); 113 | } 114 | 115 | 116 | public void close() throws IOException 117 | { 118 | // Close the delegate writer. 119 | dataEntryWriter.close(); 120 | } 121 | 122 | 123 | public void println(PrintWriter pw, String prefix) 124 | { 125 | pw.println(prefix + "JbcDataEntryWriter"); 126 | dataEntryWriter.println(pw, prefix + " "); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pga-lib/src/test/kotlin/com/guardsquare/proguard/AssemblerErrorsTest.kt: -------------------------------------------------------------------------------- 1 | package com.guardsquare.proguard 2 | 3 | import com.guardsquare.proguard.assembler.ClassParser 4 | import com.guardsquare.proguard.assembler.ParseException 5 | import com.guardsquare.proguard.assembler.Parser 6 | import io.kotest.assertions.throwables.shouldThrow 7 | import io.kotest.core.spec.style.FreeSpec 8 | import proguard.classfile.ProgramClass 9 | import java.io.StringReader 10 | 11 | /** 12 | * These tests check that various invalid code snippets are causing in an exceptions thrown from the 13 | * assembler. 14 | */ 15 | class AssemblerErrorsTest : FreeSpec({ 16 | 17 | "Variable types on the stack do not match instruction using them" { 18 | shouldThrow { 19 | buildProgramClass( 20 | """ 21 | public class AssemblerErrorsTester extends java.lang.Object { 22 | public long test() { 23 | iconst_3 24 | iconst_1 25 | lsub 26 | lreturn 27 | } 28 | } 29 | """, 30 | ) 31 | } 32 | } 33 | 34 | "Swap needs two elements on the stack" { 35 | shouldThrow { 36 | buildProgramClass( 37 | """ 38 | public class AssemblerErrorsTester extends java.lang.Object { 39 | public void test() { 40 | iconst_5 41 | swap 42 | return 43 | } 44 | } 45 | """, 46 | ) 47 | } 48 | } 49 | 50 | "Duplicate top value of an empty stack" { 51 | shouldThrow { 52 | buildProgramClass( 53 | """ 54 | public class AssemblerErrorsTester extends java.lang.Object { 55 | public void test() { 56 | dup 57 | return 58 | } 59 | } 60 | """, 61 | ) 62 | } 63 | } 64 | 65 | "Illegal bytecode instruction" { 66 | // `apples` is not a valid bytecode instructions and this should be clearly indicated by the PGA 67 | // See: https://github.com/Guardsquare/proguard-assembler/issues/8 68 | shouldThrow { 69 | buildProgramClass( 70 | """ 71 | public class AssemblerErrorsTester extends java.lang.Object { 72 | public java.lang.Object test() { 73 | apples 74 | aload_0 75 | areturn 76 | } 77 | } 78 | """, 79 | ) 80 | } 81 | } 82 | 83 | "`goto` to an invalid position" { 84 | shouldThrow { 85 | buildProgramClass( 86 | """ 87 | public class AssemblerErrorsTester extends java.lang.Object { 88 | public void test() { 89 | goto jafar 90 | return 91 | } 92 | } 93 | """, 94 | ) 95 | } 96 | } 97 | 98 | "bipush with invalid operand label" { 99 | // `bipush` expects a byte value but 300 exceeds the maximum byte value (>255) 100 | // If you want to fix this, you need to be in visitCodeAttribute:226 in the instructionParser (of the assembler) 101 | // Additionally you change the error to hande null for the clazz and method. (Just write something like "non-existing" or "null") 102 | shouldThrow { 103 | buildProgramClass( 104 | """ 105 | public class AssemblerErrorsTester extends java.lang.Object { 106 | public java.lang.Object test() { 107 | bipush 300 108 | aload_0 109 | areturn 110 | } 111 | } 112 | """, 113 | ) 114 | } 115 | } 116 | }) 117 | 118 | /** 119 | * Helper function to build and parse a java bytecode class from a string 120 | */ 121 | fun buildProgramClass(jbc: String) { 122 | val programClass = ProgramClass() 123 | programClass.accept( 124 | ClassParser( 125 | Parser( 126 | StringReader(jbc), 127 | ), 128 | ), 129 | ) 130 | } 131 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' 4 | } 5 | 6 | 7 | rootProject.name = 'proguard-assembler' 8 | include 'pga-lib' 9 | include 'pga-cli' 10 | --------------------------------------------------------------------------------