diff --git a/app/build.gradle b/app/build.gradle index 6521ac850..510c7d604 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 27 - buildToolsVersion '27.0.3' + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { applicationId "ru.playsoftware.j2meloader" - minSdkVersion 14 - targetSdkVersion 27 + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion versionCode 27 versionName "1.2.7.5" setProperty("archivesBaseName", "J2ME_Loader-$versionName") @@ -63,5 +63,5 @@ dependencies { implementation 'com.github.yukuku:ambilwarna:2.0.1' implementation 'org.ow2.asm:asm-tree:5.2' implementation 'org.sufficientlysecure:donations:2.5' - implementation fileTree(include: ['*.jar', '*.so'], dir: 'libs') + implementation project(':dexlib') } diff --git a/app/libs/NOTICE.txt b/app/libs/NOTICE.txt deleted file mode 100644 index 0e829263e..000000000 --- a/app/libs/NOTICE.txt +++ /dev/null @@ -1,197 +0,0 @@ -Notices for files contained in the libs directory: -============================================================ -Notices for file(s): -dx.jar ------------------------------------------------------------- - - Copyright (c) 2005-2008, The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - -============================================================ diff --git a/app/libs/dx.jar b/app/libs/dx.jar deleted file mode 100644 index e987f8ea9..000000000 Binary files a/app/libs/dx.jar and /dev/null differ diff --git a/app/src/main/java/ru/playsoftware/j2meloader/util/JarConverter.java b/app/src/main/java/ru/playsoftware/j2meloader/util/JarConverter.java index b8e0c56d1..b53bb91ec 100644 --- a/app/src/main/java/ru/playsoftware/j2meloader/util/JarConverter.java +++ b/app/src/main/java/ru/playsoftware/j2meloader/util/JarConverter.java @@ -22,14 +22,11 @@ import android.os.AsyncTask; import android.util.Log; import android.widget.Toast; - -import com.android.dx.command.Main; - +import com.android.dx.command.dexer.Main; import org.acra.ACRA; import org.microemu.android.asm.AndroidProducer; import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; import java.util.zip.ZipException; @@ -81,6 +78,7 @@ protected Boolean doInBackground(String... p1) { try { ZipUtils.unzip(fixedJar, dirTmp); } catch (IOException e) { + e.printStackTrace(); err = "Brocken jar"; deleteTemp(); return false; @@ -97,9 +95,16 @@ protected Boolean doInBackground(String... p1) { FileUtils.deleteDirectory(appConverted); appConverted.mkdirs(); Log.d(TAG, "appConverted=" + appConverted.getPath()); - Main.main(new String[]{ - "--dex", "--no-optimize", "--output=" + appConverted.getPath() - + ConfigActivity.MIDLET_DEX_FILE, fixedJar.getAbsolutePath()}); + try { + Main.main(new String[]{ + "--no-optimize", "--output=" + appConverted.getPath() + + ConfigActivity.MIDLET_DEX_FILE, fixedJar.getAbsolutePath()}); + } catch (IOException e) { + e.printStackTrace(); + err = "Can't convert"; + deleteTemp(); + return false; + } File conf = new File(dirTmp, "/META-INF/MANIFEST.MF"); try { FileUtils.copyFileUsingChannel(conf, new File(appConverted, ConfigActivity.MIDLET_CONF_FILE)); diff --git a/build.gradle b/build.gradle index 980f721a7..d95f102e2 100644 --- a/build.gradle +++ b/build.gradle @@ -16,4 +16,12 @@ allprojects { jcenter() google() } +} + +ext { + buildToolsVersion = '27.0.3' + compileSdkVersion = 27 + + minSdkVersion = 14 + targetSdkVersion = 27 } \ No newline at end of file diff --git a/dexlib/build.gradle b/dexlib/build.gradle new file mode 100644 index 000000000..abe0267a4 --- /dev/null +++ b/dexlib/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.13" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/dexlib/proguard-rules.pro b/dexlib/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/dexlib/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/dexlib/src/main/AndroidManifest.xml b/dexlib/src/main/AndroidManifest.xml new file mode 100644 index 000000000..ae2ca7872 --- /dev/null +++ b/dexlib/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/dexlib/src/main/java/com/android/dex/Annotation.java b/dexlib/src/main/java/com/android/dex/Annotation.java new file mode 100644 index 000000000..e5ef9783b --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/Annotation.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import static com.android.dex.EncodedValueReader.ENCODED_ANNOTATION; + +/** + * An annotation. + */ +public final class Annotation implements Comparable { + private final Dex dex; + private final byte visibility; + private final EncodedValue encodedAnnotation; + + public Annotation(Dex dex, byte visibility, EncodedValue encodedAnnotation) { + this.dex = dex; + this.visibility = visibility; + this.encodedAnnotation = encodedAnnotation; + } + + public byte getVisibility() { + return visibility; + } + + public EncodedValueReader getReader() { + return new EncodedValueReader(encodedAnnotation, ENCODED_ANNOTATION); + } + + public int getTypeIndex() { + EncodedValueReader reader = getReader(); + reader.readAnnotation(); + return reader.getAnnotationType(); + } + + public void writeTo(Dex.Section out) { + out.writeByte(visibility); + encodedAnnotation.writeTo(out); + } + + @Override public int compareTo(Annotation other) { + return encodedAnnotation.compareTo(other.encodedAnnotation); + } + + @Override public String toString() { + return dex == null + ? visibility + " " + getTypeIndex() + : visibility + " " + dex.typeNames().get(getTypeIndex()); + } +} diff --git a/dexlib/src/main/java/com/android/dex/ClassData.java b/dexlib/src/main/java/com/android/dex/ClassData.java new file mode 100644 index 000000000..840756c5b --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/ClassData.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +public final class ClassData { + private final Field[] staticFields; + private final Field[] instanceFields; + private final Method[] directMethods; + private final Method[] virtualMethods; + + public ClassData(Field[] staticFields, Field[] instanceFields, + Method[] directMethods, Method[] virtualMethods) { + this.staticFields = staticFields; + this.instanceFields = instanceFields; + this.directMethods = directMethods; + this.virtualMethods = virtualMethods; + } + + public Field[] getStaticFields() { + return staticFields; + } + + public Field[] getInstanceFields() { + return instanceFields; + } + + public Method[] getDirectMethods() { + return directMethods; + } + + public Method[] getVirtualMethods() { + return virtualMethods; + } + + public Field[] allFields() { + Field[] result = new Field[staticFields.length + instanceFields.length]; + System.arraycopy(staticFields, 0, result, 0, staticFields.length); + System.arraycopy(instanceFields, 0, result, staticFields.length, instanceFields.length); + return result; + } + + public Method[] allMethods() { + Method[] result = new Method[directMethods.length + virtualMethods.length]; + System.arraycopy(directMethods, 0, result, 0, directMethods.length); + System.arraycopy(virtualMethods, 0, result, directMethods.length, virtualMethods.length); + return result; + } + + public static class Field { + private final int fieldIndex; + private final int accessFlags; + + public Field(int fieldIndex, int accessFlags) { + this.fieldIndex = fieldIndex; + this.accessFlags = accessFlags; + } + + public int getFieldIndex() { + return fieldIndex; + } + + public int getAccessFlags() { + return accessFlags; + } + } + + public static class Method { + private final int methodIndex; + private final int accessFlags; + private final int codeOffset; + + public Method(int methodIndex, int accessFlags, int codeOffset) { + this.methodIndex = methodIndex; + this.accessFlags = accessFlags; + this.codeOffset = codeOffset; + } + + public int getMethodIndex() { + return methodIndex; + } + + public int getAccessFlags() { + return accessFlags; + } + + public int getCodeOffset() { + return codeOffset; + } + } +} diff --git a/dexlib/src/main/java/com/android/dex/ClassDef.java b/dexlib/src/main/java/com/android/dex/ClassDef.java new file mode 100644 index 000000000..b3225ec0e --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/ClassDef.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +/** + * A type definition. + */ +public final class ClassDef { + public static final int NO_INDEX = -1; + private final Dex buffer; + private final int offset; + private final int typeIndex; + private final int accessFlags; + private final int supertypeIndex; + private final int interfacesOffset; + private final int sourceFileIndex; + private final int annotationsOffset; + private final int classDataOffset; + private final int staticValuesOffset; + + public ClassDef(Dex buffer, int offset, int typeIndex, int accessFlags, + int supertypeIndex, int interfacesOffset, int sourceFileIndex, + int annotationsOffset, int classDataOffset, int staticValuesOffset) { + this.buffer = buffer; + this.offset = offset; + this.typeIndex = typeIndex; + this.accessFlags = accessFlags; + this.supertypeIndex = supertypeIndex; + this.interfacesOffset = interfacesOffset; + this.sourceFileIndex = sourceFileIndex; + this.annotationsOffset = annotationsOffset; + this.classDataOffset = classDataOffset; + this.staticValuesOffset = staticValuesOffset; + } + + public int getOffset() { + return offset; + } + + public int getTypeIndex() { + return typeIndex; + } + + public int getSupertypeIndex() { + return supertypeIndex; + } + + public int getInterfacesOffset() { + return interfacesOffset; + } + + public short[] getInterfaces() { + return buffer.readTypeList(interfacesOffset).getTypes(); + } + + public int getAccessFlags() { + return accessFlags; + } + + public int getSourceFileIndex() { + return sourceFileIndex; + } + + public int getAnnotationsOffset() { + return annotationsOffset; + } + + public int getClassDataOffset() { + return classDataOffset; + } + + public int getStaticValuesOffset() { + return staticValuesOffset; + } + + @Override public String toString() { + if (buffer == null) { + return typeIndex + " " + supertypeIndex; + } + + StringBuilder result = new StringBuilder(); + result.append(buffer.typeNames().get(typeIndex)); + if (supertypeIndex != NO_INDEX) { + result.append(" extends ").append(buffer.typeNames().get(supertypeIndex)); + } + return result.toString(); + } +} diff --git a/dexlib/src/main/java/com/android/dex/Code.java b/dexlib/src/main/java/com/android/dex/Code.java new file mode 100644 index 000000000..9258af795 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/Code.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +public final class Code { + private final int registersSize; + private final int insSize; + private final int outsSize; + private final int debugInfoOffset; + private final short[] instructions; + private final Try[] tries; + private final CatchHandler[] catchHandlers; + + public Code(int registersSize, int insSize, int outsSize, int debugInfoOffset, + short[] instructions, Try[] tries, CatchHandler[] catchHandlers) { + this.registersSize = registersSize; + this.insSize = insSize; + this.outsSize = outsSize; + this.debugInfoOffset = debugInfoOffset; + this.instructions = instructions; + this.tries = tries; + this.catchHandlers = catchHandlers; + } + + public int getRegistersSize() { + return registersSize; + } + + public int getInsSize() { + return insSize; + } + + public int getOutsSize() { + return outsSize; + } + + public int getDebugInfoOffset() { + return debugInfoOffset; + } + + public short[] getInstructions() { + return instructions; + } + + public Try[] getTries() { + return tries; + } + + public CatchHandler[] getCatchHandlers() { + return catchHandlers; + } + + public static class Try { + final int startAddress; + final int instructionCount; + final int catchHandlerIndex; + + Try(int startAddress, int instructionCount, int catchHandlerIndex) { + this.startAddress = startAddress; + this.instructionCount = instructionCount; + this.catchHandlerIndex = catchHandlerIndex; + } + + public int getStartAddress() { + return startAddress; + } + + public int getInstructionCount() { + return instructionCount; + } + + /** + * Returns this try's catch handler index. Note that + * this is distinct from the its catch handler offset. + */ + public int getCatchHandlerIndex() { + return catchHandlerIndex; + } + } + + public static class CatchHandler { + final int[] typeIndexes; + final int[] addresses; + final int catchAllAddress; + final int offset; + + public CatchHandler(int[] typeIndexes, int[] addresses, int catchAllAddress, int offset) { + this.typeIndexes = typeIndexes; + this.addresses = addresses; + this.catchAllAddress = catchAllAddress; + this.offset = offset; + } + + public int[] getTypeIndexes() { + return typeIndexes; + } + + public int[] getAddresses() { + return addresses; + } + + public int getCatchAllAddress() { + return catchAllAddress; + } + + public int getOffset() { + return offset; + } + } +} diff --git a/dexlib/src/main/java/com/android/dex/Dex.java b/dexlib/src/main/java/com/android/dex/Dex.java new file mode 100644 index 000000000..16293262c --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/Dex.java @@ -0,0 +1,791 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.Code.CatchHandler; +import com.android.dex.Code.Try; +import com.android.dex.util.ByteInput; +import com.android.dex.util.ByteOutput; +import com.android.dex.util.FileUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UTFDataFormatException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.AbstractList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.RandomAccess; +import java.util.zip.Adler32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * The bytes of a dex file in memory for reading and writing. All int offsets + * are unsigned. + */ +public final class Dex { + private static final int CHECKSUM_OFFSET = 8; + private static final int CHECKSUM_SIZE = 4; + private static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + CHECKSUM_SIZE; + private static final int SIGNATURE_SIZE = 20; + // Provided as a convenience to avoid a memory allocation to benefit Dalvik. + // Note: libcore.util.EmptyArray cannot be accessed when this code isn't run on Dalvik. + static final short[] EMPTY_SHORT_ARRAY = new short[0]; + + private ByteBuffer data; + private final TableOfContents tableOfContents = new TableOfContents(); + private int nextSectionStart = 0; + private final StringTable strings = new StringTable(); + private final TypeIndexToDescriptorIndexTable typeIds = new TypeIndexToDescriptorIndexTable(); + private final TypeIndexToDescriptorTable typeNames = new TypeIndexToDescriptorTable(); + private final ProtoIdTable protoIds = new ProtoIdTable(); + private final FieldIdTable fieldIds = new FieldIdTable(); + private final MethodIdTable methodIds = new MethodIdTable(); + + /** + * Creates a new dex that reads from {@code data}. It is an error to modify + * {@code data} after using it to create a dex buffer. + */ + public Dex(byte[] data) throws IOException { + this(ByteBuffer.wrap(data)); + } + + private Dex(ByteBuffer data) throws IOException { + this.data = data; + this.data.order(ByteOrder.LITTLE_ENDIAN); + this.tableOfContents.readFrom(this); + } + + /** + * Creates a new empty dex of the specified size. + */ + public Dex(int byteCount) throws IOException { + this.data = ByteBuffer.wrap(new byte[byteCount]); + this.data.order(ByteOrder.LITTLE_ENDIAN); + } + + /** + * Creates a new dex buffer of the dex in {@code in}, and closes {@code in}. + */ + public Dex(InputStream in) throws IOException { + try { + loadFrom(in); + } finally { + in.close(); + } + } + + /** + * Creates a new dex buffer from the dex file {@code file}. + */ + public Dex(File file) throws IOException { + if (FileUtils.hasArchiveSuffix(file.getName())) { + ZipFile zipFile = new ZipFile(file); + ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME); + if (entry != null) { + try (InputStream inputStream = zipFile.getInputStream(entry)) { + loadFrom(inputStream); + } + zipFile.close(); + } else { + throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file); + } + } else if (file.getName().endsWith(".dex")) { + try (InputStream inputStream = new FileInputStream(file)) { + loadFrom(inputStream); + } + } else { + throw new DexException("unknown output extension: " + file); + } + } + + /** + * It is the caller's responsibility to close {@code in}. + */ + private void loadFrom(InputStream in) throws IOException { + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + + int count; + while ((count = in.read(buffer)) != -1) { + bytesOut.write(buffer, 0, count); + } + + this.data = ByteBuffer.wrap(bytesOut.toByteArray()); + this.data.order(ByteOrder.LITTLE_ENDIAN); + this.tableOfContents.readFrom(this); + } + + private static void checkBounds(int index, int length) { + if (index < 0 || index >= length) { + throw new IndexOutOfBoundsException("index:" + index + ", length=" + length); + } + } + + public void writeTo(OutputStream out) throws IOException { + byte[] buffer = new byte[8192]; + ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe + data.clear(); + while (data.hasRemaining()) { + int count = Math.min(buffer.length, data.remaining()); + data.get(buffer, 0, count); + out.write(buffer, 0, count); + } + } + + public void writeTo(File dexOut) throws IOException { + try (OutputStream out = new FileOutputStream(dexOut)) { + writeTo(out); + } + } + + public TableOfContents getTableOfContents() { + return tableOfContents; + } + + public Section open(int position) { + if (position < 0 || position >= data.capacity()) { + throw new IllegalArgumentException("position=" + position + + " length=" + data.capacity()); + } + ByteBuffer sectionData = data.duplicate(); + sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? + sectionData.position(position); + sectionData.limit(data.capacity()); + return new Section("section", sectionData); + } + + public Section appendSection(int maxByteCount, String name) { + if ((maxByteCount & 3) != 0) { + throw new IllegalStateException("Not four byte aligned!"); + } + int limit = nextSectionStart + maxByteCount; + ByteBuffer sectionData = data.duplicate(); + sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary? + sectionData.position(nextSectionStart); + sectionData.limit(limit); + Section result = new Section(name, sectionData); + nextSectionStart = limit; + return result; + } + + public int getLength() { + return data.capacity(); + } + + public int getNextSectionStart() { + return nextSectionStart; + } + + /** + * Returns a copy of the the bytes of this dex. + */ + public byte[] getBytes() { + ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe + byte[] result = new byte[data.capacity()]; + data.position(0); + data.get(result); + return result; + } + + public List strings() { + return strings; + } + + public List typeIds() { + return typeIds; + } + + public List typeNames() { + return typeNames; + } + + public List protoIds() { + return protoIds; + } + + public List fieldIds() { + return fieldIds; + } + + public List methodIds() { + return methodIds; + } + + public Iterable classDefs() { + return new ClassDefIterable(); + } + + public TypeList readTypeList(int offset) { + if (offset == 0) { + return TypeList.EMPTY; + } + return open(offset).readTypeList(); + } + + public ClassData readClassData(ClassDef classDef) { + int offset = classDef.getClassDataOffset(); + if (offset == 0) { + throw new IllegalArgumentException("offset == 0"); + } + return open(offset).readClassData(); + } + + public Code readCode(ClassData.Method method) { + int offset = method.getCodeOffset(); + if (offset == 0) { + throw new IllegalArgumentException("offset == 0"); + } + return open(offset).readCode(); + } + + /** + * Returns the signature of all but the first 32 bytes of this dex. The + * first 32 bytes of dex files are not specified to be included in the + * signature. + */ + public byte[] computeSignature() throws IOException { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(); + } + byte[] buffer = new byte[8192]; + ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe + data.limit(data.capacity()); + data.position(SIGNATURE_OFFSET + SIGNATURE_SIZE); + while (data.hasRemaining()) { + int count = Math.min(buffer.length, data.remaining()); + data.get(buffer, 0, count); + digest.update(buffer, 0, count); + } + return digest.digest(); + } + + /** + * Returns the checksum of all but the first 12 bytes of {@code dex}. + */ + public int computeChecksum() throws IOException { + Adler32 adler32 = new Adler32(); + byte[] buffer = new byte[8192]; + ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe + data.limit(data.capacity()); + data.position(CHECKSUM_OFFSET + CHECKSUM_SIZE); + while (data.hasRemaining()) { + int count = Math.min(buffer.length, data.remaining()); + data.get(buffer, 0, count); + adler32.update(buffer, 0, count); + } + return (int) adler32.getValue(); + } + + /** + * Generates the signature and checksum of the dex file {@code out} and + * writes them to the file. + */ + public void writeHashes() throws IOException { + open(SIGNATURE_OFFSET).write(computeSignature()); + open(CHECKSUM_OFFSET).writeInt(computeChecksum()); + } + + /** + * Look up a descriptor index from a type index. Cheaper than: + * {@code open(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();} + */ + public int descriptorIndexFromTypeIndex(int typeIndex) { + checkBounds(typeIndex, tableOfContents.typeIds.size); + int position = tableOfContents.typeIds.off + (SizeOf.TYPE_ID_ITEM * typeIndex); + return data.getInt(position); + } + + + public final class Section implements ByteInput, ByteOutput { + private final String name; + private final ByteBuffer data; + private final int initialPosition; + + private Section(String name, ByteBuffer data) { + this.name = name; + this.data = data; + this.initialPosition = data.position(); + } + + public int getPosition() { + return data.position(); + } + + public int readInt() { + return data.getInt(); + } + + public short readShort() { + return data.getShort(); + } + + public int readUnsignedShort() { + return readShort() & 0xffff; + } + + public byte readByte() { + return data.get(); + } + + public byte[] readByteArray(int length) { + byte[] result = new byte[length]; + data.get(result); + return result; + } + + public short[] readShortArray(int length) { + if (length == 0) { + return EMPTY_SHORT_ARRAY; + } + short[] result = new short[length]; + for (int i = 0; i < length; i++) { + result[i] = readShort(); + } + return result; + } + + public int readUleb128() { + return Leb128.readUnsignedLeb128(this); + } + + public int readUleb128p1() { + return Leb128.readUnsignedLeb128(this) - 1; + } + + public int readSleb128() { + return Leb128.readSignedLeb128(this); + } + + public void writeUleb128p1(int i) { + writeUleb128(i + 1); + } + + public TypeList readTypeList() { + int size = readInt(); + short[] types = readShortArray(size); + alignToFourBytes(); + return new TypeList(Dex.this, types); + } + + public String readString() { + int offset = readInt(); + int savedPosition = data.position(); + int savedLimit = data.limit(); + data.position(offset); + data.limit(data.capacity()); + try { + int expectedLength = readUleb128(); + String result = Mutf8.decode(this, new char[expectedLength]); + if (result.length() != expectedLength) { + throw new DexException("Declared length " + expectedLength + + " doesn't match decoded length of " + result.length()); + } + return result; + } catch (UTFDataFormatException e) { + throw new DexException(e); + } finally { + data.position(savedPosition); + data.limit(savedLimit); + } + } + + public FieldId readFieldId() { + int declaringClassIndex = readUnsignedShort(); + int typeIndex = readUnsignedShort(); + int nameIndex = readInt(); + return new FieldId(Dex.this, declaringClassIndex, typeIndex, nameIndex); + } + + public MethodId readMethodId() { + int declaringClassIndex = readUnsignedShort(); + int protoIndex = readUnsignedShort(); + int nameIndex = readInt(); + return new MethodId(Dex.this, declaringClassIndex, protoIndex, nameIndex); + } + + public ProtoId readProtoId() { + int shortyIndex = readInt(); + int returnTypeIndex = readInt(); + int parametersOffset = readInt(); + return new ProtoId(Dex.this, shortyIndex, returnTypeIndex, parametersOffset); + } + + public ClassDef readClassDef() { + int offset = getPosition(); + int type = readInt(); + int accessFlags = readInt(); + int supertype = readInt(); + int interfacesOffset = readInt(); + int sourceFileIndex = readInt(); + int annotationsOffset = readInt(); + int classDataOffset = readInt(); + int staticValuesOffset = readInt(); + return new ClassDef(Dex.this, offset, type, accessFlags, supertype, + interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset, + staticValuesOffset); + } + + private Code readCode() { + int registersSize = readUnsignedShort(); + int insSize = readUnsignedShort(); + int outsSize = readUnsignedShort(); + int triesSize = readUnsignedShort(); + int debugInfoOffset = readInt(); + int instructionsSize = readInt(); + short[] instructions = readShortArray(instructionsSize); + Try[] tries; + CatchHandler[] catchHandlers; + if (triesSize > 0) { + if (instructions.length % 2 == 1) { + readShort(); // padding + } + + /* + * We can't read the tries until we've read the catch handlers. + * Unfortunately they're in the opposite order in the dex file + * so we need to read them out-of-order. + */ + Section triesSection = open(data.position()); + skip(triesSize * SizeOf.TRY_ITEM); + catchHandlers = readCatchHandlers(); + tries = triesSection.readTries(triesSize, catchHandlers); + } else { + tries = new Try[0]; + catchHandlers = new CatchHandler[0]; + } + return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions, + tries, catchHandlers); + } + + private CatchHandler[] readCatchHandlers() { + int baseOffset = data.position(); + int catchHandlersSize = readUleb128(); + CatchHandler[] result = new CatchHandler[catchHandlersSize]; + for (int i = 0; i < catchHandlersSize; i++) { + int offset = data.position() - baseOffset; + result[i] = readCatchHandler(offset); + } + return result; + } + + private Try[] readTries(int triesSize, CatchHandler[] catchHandlers) { + Try[] result = new Try[triesSize]; + for (int i = 0; i < triesSize; i++) { + int startAddress = readInt(); + int instructionCount = readUnsignedShort(); + int handlerOffset = readUnsignedShort(); + int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset); + result[i] = new Try(startAddress, instructionCount, catchHandlerIndex); + } + return result; + } + + private int findCatchHandlerIndex(CatchHandler[] catchHandlers, int offset) { + for (int i = 0; i < catchHandlers.length; i++) { + CatchHandler catchHandler = catchHandlers[i]; + if (catchHandler.getOffset() == offset) { + return i; + } + } + throw new IllegalArgumentException(); + } + + private CatchHandler readCatchHandler(int offset) { + int size = readSleb128(); + int handlersCount = Math.abs(size); + int[] typeIndexes = new int[handlersCount]; + int[] addresses = new int[handlersCount]; + for (int i = 0; i < handlersCount; i++) { + typeIndexes[i] = readUleb128(); + addresses[i] = readUleb128(); + } + int catchAllAddress = size <= 0 ? readUleb128() : -1; + return new CatchHandler(typeIndexes, addresses, catchAllAddress, offset); + } + + private ClassData readClassData() { + int staticFieldsSize = readUleb128(); + int instanceFieldsSize = readUleb128(); + int directMethodsSize = readUleb128(); + int virtualMethodsSize = readUleb128(); + ClassData.Field[] staticFields = readFields(staticFieldsSize); + ClassData.Field[] instanceFields = readFields(instanceFieldsSize); + ClassData.Method[] directMethods = readMethods(directMethodsSize); + ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize); + return new ClassData(staticFields, instanceFields, directMethods, virtualMethods); + } + + private ClassData.Field[] readFields(int count) { + ClassData.Field[] result = new ClassData.Field[count]; + int fieldIndex = 0; + for (int i = 0; i < count; i++) { + fieldIndex += readUleb128(); // field index diff + int accessFlags = readUleb128(); + result[i] = new ClassData.Field(fieldIndex, accessFlags); + } + return result; + } + + private ClassData.Method[] readMethods(int count) { + ClassData.Method[] result = new ClassData.Method[count]; + int methodIndex = 0; + for (int i = 0; i < count; i++) { + methodIndex += readUleb128(); // method index diff + int accessFlags = readUleb128(); + int codeOff = readUleb128(); + result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff); + } + return result; + } + + /** + * Returns a byte array containing the bytes from {@code start} to this + * section's current position. + */ + private byte[] getBytesFrom(int start) { + int end = data.position(); + byte[] result = new byte[end - start]; + data.position(start); + data.get(result); + return result; + } + + public Annotation readAnnotation() { + byte visibility = readByte(); + int start = data.position(); + new EncodedValueReader(this, EncodedValueReader.ENCODED_ANNOTATION).skipValue(); + return new Annotation(Dex.this, visibility, new EncodedValue(getBytesFrom(start))); + } + + public EncodedValue readEncodedArray() { + int start = data.position(); + new EncodedValueReader(this, EncodedValueReader.ENCODED_ARRAY).skipValue(); + return new EncodedValue(getBytesFrom(start)); + } + + public void skip(int count) { + if (count < 0) { + throw new IllegalArgumentException(); + } + data.position(data.position() + count); + } + + /** + * Skips bytes until the position is aligned to a multiple of 4. + */ + public void alignToFourBytes() { + data.position((data.position() + 3) & ~3); + } + + /** + * Writes 0x00 until the position is aligned to a multiple of 4. + */ + public void alignToFourBytesWithZeroFill() { + while ((data.position() & 3) != 0) { + data.put((byte) 0); + } + } + + public void assertFourByteAligned() { + if ((data.position() & 3) != 0) { + throw new IllegalStateException("Not four byte aligned!"); + } + } + + public void write(byte[] bytes) { + this.data.put(bytes); + } + + public void writeByte(int b) { + data.put((byte) b); + } + + public void writeShort(short i) { + data.putShort(i); + } + + public void writeUnsignedShort(int i) { + short s = (short) i; + if (i != (s & 0xffff)) { + throw new IllegalArgumentException("Expected an unsigned short: " + i); + } + writeShort(s); + } + + public void write(short[] shorts) { + for (short s : shorts) { + writeShort(s); + } + } + + public void writeInt(int i) { + data.putInt(i); + } + + public void writeUleb128(int i) { + try { + Leb128.writeUnsignedLeb128(this, i); + } catch (ArrayIndexOutOfBoundsException e) { + throw new DexException("Section limit " + data.limit() + " exceeded by " + name); + } + } + + public void writeSleb128(int i) { + try { + Leb128.writeSignedLeb128(this, i); + } catch (ArrayIndexOutOfBoundsException e) { + throw new DexException("Section limit " + data.limit() + " exceeded by " + name); + } + } + + public void writeStringData(String value) { + try { + int length = value.length(); + writeUleb128(length); + write(Mutf8.encode(value)); + writeByte(0); + } catch (UTFDataFormatException e) { + throw new AssertionError(); + } + } + + public void writeTypeList(TypeList typeList) { + short[] types = typeList.getTypes(); + writeInt(types.length); + for (short type : types) { + writeShort(type); + } + alignToFourBytesWithZeroFill(); + } + + /** + * Returns the number of bytes used by this section. + */ + public int used() { + return data.position() - initialPosition; + } + } + + private final class StringTable extends AbstractList implements RandomAccess { + @Override public String get(int index) { + checkBounds(index, tableOfContents.stringIds.size); + return open(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM)) + .readString(); + } + @Override public int size() { + return tableOfContents.stringIds.size; + } + } + + private final class TypeIndexToDescriptorIndexTable extends AbstractList + implements RandomAccess { + @Override public Integer get(int index) { + return descriptorIndexFromTypeIndex(index); + } + @Override public int size() { + return tableOfContents.typeIds.size; + } + } + + private final class TypeIndexToDescriptorTable extends AbstractList + implements RandomAccess { + @Override public String get(int index) { + return strings.get(descriptorIndexFromTypeIndex(index)); + } + @Override public int size() { + return tableOfContents.typeIds.size; + } + } + + private final class ProtoIdTable extends AbstractList implements RandomAccess { + @Override public ProtoId get(int index) { + checkBounds(index, tableOfContents.protoIds.size); + return open(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index)) + .readProtoId(); + } + @Override public int size() { + return tableOfContents.protoIds.size; + } + } + + private final class FieldIdTable extends AbstractList implements RandomAccess { + @Override public FieldId get(int index) { + checkBounds(index, tableOfContents.fieldIds.size); + return open(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index)) + .readFieldId(); + } + @Override public int size() { + return tableOfContents.fieldIds.size; + } + } + + private final class MethodIdTable extends AbstractList implements RandomAccess { + @Override public MethodId get(int index) { + checkBounds(index, tableOfContents.methodIds.size); + return open(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index)) + .readMethodId(); + } + @Override public int size() { + return tableOfContents.methodIds.size; + } + } + + private final class ClassDefIterator implements Iterator { + private final Dex.Section in = open(tableOfContents.classDefs.off); + private int count = 0; + + @Override + public boolean hasNext() { + return count < tableOfContents.classDefs.size; + } + @Override + public ClassDef next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + count++; + return in.readClassDef(); + } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private final class ClassDefIterable implements Iterable { + public Iterator iterator() { + return !tableOfContents.classDefs.exists() + ? Collections.emptySet().iterator() + : new ClassDefIterator(); + } + } +} diff --git a/dexlib/src/main/java/com/android/dex/DexException.java b/dexlib/src/main/java/com/android/dex/DexException.java new file mode 100644 index 000000000..ee0af18f9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/DexException.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.ExceptionWithContext; + +/** + * Thrown when there's a format problem reading, writing, or generally + * processing a dex file. + */ +public class DexException extends ExceptionWithContext { + public DexException(String message) { + super(message); + } + + public DexException(Throwable cause) { + super(cause); + } +} diff --git a/dexlib/src/main/java/com/android/dex/DexFormat.java b/dexlib/src/main/java/com/android/dex/DexFormat.java new file mode 100644 index 000000000..97771d813 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/DexFormat.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +/** + * Constants that show up in and are otherwise related to {@code .dex} + * files, and helper methods for same. + */ +public final class DexFormat { + private DexFormat() {} + + /** API level to target in order to generate invoke-polymorphic */ + public static final int API_INVOKE_POLYMORPHIC = 26; + + /** API level to target in order to pass through default and static interface methods */ + public static final int API_DEFAULT_INTERFACE_METHODS = 24; + + /** API level to target in order to suppress extended opcode usage */ + public static final int API_NO_EXTENDED_OPCODES = 13; + + /** + * API level to target in order to produce the most modern file + * format + */ + public static final int API_CURRENT = API_INVOKE_POLYMORPHIC; + + /** dex file version number for API level 26 and earlier */ + public static final String VERSION_FOR_API_26 = "038"; + + /** dex file version number for API level 24 and earlier */ + public static final String VERSION_FOR_API_24 = "037"; + + /** dex file version number for API level 13 and earlier */ + public static final String VERSION_FOR_API_13 = "035"; + + /** + * Dex file version number for dalvik. + *

+ * Note: Dex version 36 was loadable in some versions of Dalvik but was never fully supported or + * completed and is not considered a valid dex file format. + *

+ */ + public static final String VERSION_CURRENT = VERSION_FOR_API_26; + + /** + * file name of the primary {@code .dex} file inside an + * application or library {@code .jar} file + */ + public static final String DEX_IN_JAR_NAME = "classes.dex"; + + /** common prefix for all dex file "magic numbers" */ + public static final String MAGIC_PREFIX = "dex\n"; + + /** common suffix for all dex file "magic numbers" */ + public static final String MAGIC_SUFFIX = "\0"; + + /** + * value used to indicate endianness of file contents + */ + public static final int ENDIAN_TAG = 0x12345678; + + /** + * Maximum addressable field or method index. + * The largest addressable member is 0xffff, in the "instruction formats" spec as field@CCCC or + * meth@CCCC. + */ + public static final int MAX_MEMBER_IDX = 0xFFFF; + + /** + * Maximum addressable type index. + * The largest addressable type is 0xffff, in the "instruction formats" spec as type@CCCC. + */ + public static final int MAX_TYPE_IDX = 0xFFFF; + + /** + * Returns the API level corresponding to the given magic number, + * or {@code -1} if the given array is not a well-formed dex file + * magic number. + */ + public static int magicToApi(byte[] magic) { + if (magic.length != 8) { + return -1; + } + + if ((magic[0] != 'd') || (magic[1] != 'e') || (magic[2] != 'x') || (magic[3] != '\n') || + (magic[7] != '\0')) { + return -1; + } + + String version = "" + ((char) magic[4]) + ((char) magic[5]) +((char) magic[6]); + + if (version.equals(VERSION_FOR_API_13)) { + return API_NO_EXTENDED_OPCODES; + } else if (version.equals(VERSION_FOR_API_24)) { + return API_DEFAULT_INTERFACE_METHODS; + } else if (version.equals(VERSION_FOR_API_26)) { + return API_INVOKE_POLYMORPHIC; + } else if (version.equals(VERSION_CURRENT)) { + return API_CURRENT; + } + + return -1; + } + + /** + * Returns the magic number corresponding to the given target API level. + */ + public static String apiToMagic(int targetApiLevel) { + String version; + + if (targetApiLevel >= API_CURRENT) { + version = VERSION_CURRENT; + } else if (targetApiLevel >= API_INVOKE_POLYMORPHIC) { + version = VERSION_FOR_API_26; + } else if (targetApiLevel >= API_DEFAULT_INTERFACE_METHODS) { + version = VERSION_FOR_API_24; + } else { + version = VERSION_FOR_API_13; + } + + return MAGIC_PREFIX + version + MAGIC_SUFFIX; + } + + public static boolean isSupportedDexMagic(byte[] magic) { + int api = magicToApi(magic); + return api > 0; + } +} diff --git a/dexlib/src/main/java/com/android/dex/DexIndexOverflowException.java b/dexlib/src/main/java/com/android/dex/DexIndexOverflowException.java new file mode 100644 index 000000000..32262072b --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/DexIndexOverflowException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +/** + * Thrown when there's an index overflow writing a dex file. + */ +public final class DexIndexOverflowException extends DexException { + public DexIndexOverflowException(String message) { + super(message); + } + + public DexIndexOverflowException(Throwable cause) { + super(cause); + } +} diff --git a/dexlib/src/main/java/com/android/dex/EncodedValue.java b/dexlib/src/main/java/com/android/dex/EncodedValue.java new file mode 100644 index 000000000..8d0c3adcf --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/EncodedValue.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.ByteArrayByteInput; +import com.android.dex.util.ByteInput; + +/** + * An encoded value or array. + */ +public final class EncodedValue implements Comparable { + private final byte[] data; + + public EncodedValue(byte[] data) { + this.data = data; + } + + public ByteInput asByteInput() { + return new ByteArrayByteInput(data); + } + + public byte[] getBytes() { + return data; + } + + public void writeTo(Dex.Section out) { + out.write(data); + } + + @Override public int compareTo(EncodedValue other) { + int size = Math.min(data.length, other.data.length); + for (int i = 0; i < size; i++) { + if (data[i] != other.data[i]) { + return (data[i] & 0xff) - (other.data[i] & 0xff); + } + } + return data.length - other.data.length; + } + + @Override public String toString() { + return Integer.toHexString(data[0] & 0xff) + "...(" + data.length + ")"; + } +} diff --git a/dexlib/src/main/java/com/android/dex/EncodedValueCodec.java b/dexlib/src/main/java/com/android/dex/EncodedValueCodec.java new file mode 100644 index 000000000..7fc172434 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/EncodedValueCodec.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.ByteInput; +import com.android.dex.util.ByteOutput; + +/** + * Read and write {@code encoded_value} primitives. + */ +public final class EncodedValueCodec { + private EncodedValueCodec() { + } + + /** + * Writes a signed integral to {@code out}. + */ + public static void writeSignedIntegralValue(ByteOutput out, int type, long value) { + /* + * Figure out how many bits are needed to represent the value, + * including a sign bit: The bit count is subtracted from 65 + * and not 64 to account for the sign bit. The xor operation + * has the effect of leaving non-negative values alone and + * unary complementing negative values (so that a leading zero + * count always returns a useful number for our present + * purpose). + */ + int requiredBits = 65 - Long.numberOfLeadingZeros(value ^ (value >> 63)); + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Writes an unsigned integral to {@code out}. + */ + public static void writeUnsignedIntegralValue(ByteOutput out, int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfLeadingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Writes a right-zero-extended value to {@code out}. + */ + public static void writeRightZeroExtendedValue(ByteOutput out, int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfTrailingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + // Scootch the first bits to be written down to the low-order bits. + value >>= 64 - (requiredBytes * 8); + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Read a signed integer. + * + * @param zwidth byte count minus one + */ + public static int readSignedInt(ByteInput in, int zwidth) { + int result = 0; + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xff) << 24); + } + result >>= (3 - zwidth) * 8; + return result; + } + + /** + * Read an unsigned integer. + * + * @param zwidth byte count minus one + * @param fillOnRight true to zero fill on the right; false on the left + */ + public static int readUnsignedInt(ByteInput in, int zwidth, boolean fillOnRight) { + int result = 0; + if (!fillOnRight) { + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xff) << 24); + } + result >>>= (3 - zwidth) * 8; + } else { + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xff) << 24); + } + } + return result; + } + + /** + * Read a signed long. + * + * @param zwidth byte count minus one + */ + public static long readSignedLong(ByteInput in, int zwidth) { + long result = 0; + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xffL) << 56); + } + result >>= (7 - zwidth) * 8; + return result; + } + + /** + * Read an unsigned long. + * + * @param zwidth byte count minus one + * @param fillOnRight true to zero fill on the right; false on the left + */ + public static long readUnsignedLong(ByteInput in, int zwidth, boolean fillOnRight) { + long result = 0; + if (!fillOnRight) { + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xffL) << 56); + } + result >>>= (7 - zwidth) * 8; + } else { + for (int i = zwidth; i >= 0; i--) { + result = (result >>> 8) | ((in.readByte() & 0xffL) << 56); + } + } + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dex/EncodedValueReader.java b/dexlib/src/main/java/com/android/dex/EncodedValueReader.java new file mode 100644 index 000000000..6f60538a2 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/EncodedValueReader.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.ByteInput; + +/** + * Pull parser for encoded values. + */ +public final class EncodedValueReader { + public static final int ENCODED_BYTE = 0x00; + public static final int ENCODED_SHORT = 0x02; + public static final int ENCODED_CHAR = 0x03; + public static final int ENCODED_INT = 0x04; + public static final int ENCODED_LONG = 0x06; + public static final int ENCODED_FLOAT = 0x10; + public static final int ENCODED_DOUBLE = 0x11; + public static final int ENCODED_STRING = 0x17; + public static final int ENCODED_TYPE = 0x18; + public static final int ENCODED_FIELD = 0x19; + public static final int ENCODED_ENUM = 0x1b; + public static final int ENCODED_METHOD = 0x1a; + public static final int ENCODED_ARRAY = 0x1c; + public static final int ENCODED_ANNOTATION = 0x1d; + public static final int ENCODED_NULL = 0x1e; + public static final int ENCODED_BOOLEAN = 0x1f; + + /** placeholder type if the type is not yet known */ + private static final int MUST_READ = -1; + + protected final ByteInput in; + private int type = MUST_READ; + private int annotationType; + private int arg; + + public EncodedValueReader(ByteInput in) { + this.in = in; + } + + public EncodedValueReader(EncodedValue in) { + this(in.asByteInput()); + } + + /** + * Creates a new encoded value reader whose only value is the specified + * known type. This is useful for encoded values without a type prefix, + * such as class_def_item's encoded_array or annotation_item's + * encoded_annotation. + */ + public EncodedValueReader(ByteInput in, int knownType) { + this.in = in; + this.type = knownType; + } + + public EncodedValueReader(EncodedValue in, int knownType) { + this(in.asByteInput(), knownType); + } + + /** + * Returns the type of the next value to read. + */ + public int peek() { + if (type == MUST_READ) { + int argAndType = in.readByte() & 0xff; + type = argAndType & 0x1f; + arg = (argAndType & 0xe0) >> 5; + } + return type; + } + + /** + * Begins reading the elements of an array, returning the array's size. The + * caller must follow up by calling a read method for each element in the + * array. For example, this reads a byte array:
   {@code
+     *   int arraySize = readArray();
+     *   for (int i = 0, i < arraySize; i++) {
+     *     readByte();
+     *   }
+     * }
+ */ + public int readArray() { + checkType(ENCODED_ARRAY); + type = MUST_READ; + return Leb128.readUnsignedLeb128(in); + } + + /** + * Begins reading the fields of an annotation, returning the number of + * fields. The caller must follow up by making alternating calls to {@link + * #readAnnotationName()} and another read method. For example, this reads + * an annotation whose fields are all bytes:
   {@code
+     *   int fieldCount = readAnnotation();
+     *   int annotationType = getAnnotationType();
+     *   for (int i = 0; i < fieldCount; i++) {
+     *       readAnnotationName();
+     *       readByte();
+     *   }
+     * }
+ */ + public int readAnnotation() { + checkType(ENCODED_ANNOTATION); + type = MUST_READ; + annotationType = Leb128.readUnsignedLeb128(in); + return Leb128.readUnsignedLeb128(in); + } + + /** + * Returns the type of the annotation just returned by {@link + * #readAnnotation()}. This method's value is undefined unless the most + * recent call was to {@link #readAnnotation()}. + */ + public int getAnnotationType() { + return annotationType; + } + + public int readAnnotationName() { + return Leb128.readUnsignedLeb128(in); + } + + public byte readByte() { + checkType(ENCODED_BYTE); + type = MUST_READ; + return (byte) EncodedValueCodec.readSignedInt(in, arg); + } + + public short readShort() { + checkType(ENCODED_SHORT); + type = MUST_READ; + return (short) EncodedValueCodec.readSignedInt(in, arg); + } + + public char readChar() { + checkType(ENCODED_CHAR); + type = MUST_READ; + return (char) EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readInt() { + checkType(ENCODED_INT); + type = MUST_READ; + return EncodedValueCodec.readSignedInt(in, arg); + } + + public long readLong() { + checkType(ENCODED_LONG); + type = MUST_READ; + return EncodedValueCodec.readSignedLong(in, arg); + } + + public float readFloat() { + checkType(ENCODED_FLOAT); + type = MUST_READ; + return Float.intBitsToFloat(EncodedValueCodec.readUnsignedInt(in, arg, true)); + } + + public double readDouble() { + checkType(ENCODED_DOUBLE); + type = MUST_READ; + return Double.longBitsToDouble(EncodedValueCodec.readUnsignedLong(in, arg, true)); + } + + public int readString() { + checkType(ENCODED_STRING); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readType() { + checkType(ENCODED_TYPE); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readField() { + checkType(ENCODED_FIELD); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readEnum() { + checkType(ENCODED_ENUM); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public int readMethod() { + checkType(ENCODED_METHOD); + type = MUST_READ; + return EncodedValueCodec.readUnsignedInt(in, arg, false); + } + + public void readNull() { + checkType(ENCODED_NULL); + type = MUST_READ; + } + + public boolean readBoolean() { + checkType(ENCODED_BOOLEAN); + type = MUST_READ; + return arg != 0; + } + + /** + * Skips a single value, including its nested values if it is an array or + * annotation. + */ + public void skipValue() { + switch (peek()) { + case ENCODED_BYTE: + readByte(); + break; + case ENCODED_SHORT: + readShort(); + break; + case ENCODED_CHAR: + readChar(); + break; + case ENCODED_INT: + readInt(); + break; + case ENCODED_LONG: + readLong(); + break; + case ENCODED_FLOAT: + readFloat(); + break; + case ENCODED_DOUBLE: + readDouble(); + break; + case ENCODED_STRING: + readString(); + break; + case ENCODED_TYPE: + readType(); + break; + case ENCODED_FIELD: + readField(); + break; + case ENCODED_ENUM: + readEnum(); + break; + case ENCODED_METHOD: + readMethod(); + break; + case ENCODED_ARRAY: + for (int i = 0, size = readArray(); i < size; i++) { + skipValue(); + } + break; + case ENCODED_ANNOTATION: + for (int i = 0, size = readAnnotation(); i < size; i++) { + readAnnotationName(); + skipValue(); + } + break; + case ENCODED_NULL: + readNull(); + break; + case ENCODED_BOOLEAN: + readBoolean(); + break; + default: + throw new DexException("Unexpected type: " + Integer.toHexString(type)); + } + } + + private void checkType(int expected) { + if (peek() != expected) { + throw new IllegalStateException( + String.format("Expected %x but was %x", expected, peek())); + } + } +} diff --git a/dexlib/src/main/java/com/android/dex/FieldId.java b/dexlib/src/main/java/com/android/dex/FieldId.java new file mode 100644 index 000000000..2f41708c8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/FieldId.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.Unsigned; + +public final class FieldId implements Comparable { + private final Dex dex; + private final int declaringClassIndex; + private final int typeIndex; + private final int nameIndex; + + public FieldId(Dex dex, int declaringClassIndex, int typeIndex, int nameIndex) { + this.dex = dex; + this.declaringClassIndex = declaringClassIndex; + this.typeIndex = typeIndex; + this.nameIndex = nameIndex; + } + + public int getDeclaringClassIndex() { + return declaringClassIndex; + } + + public int getTypeIndex() { + return typeIndex; + } + + public int getNameIndex() { + return nameIndex; + } + + public int compareTo(FieldId other) { + if (declaringClassIndex != other.declaringClassIndex) { + return Unsigned.compare(declaringClassIndex, other.declaringClassIndex); + } + if (nameIndex != other.nameIndex) { + return Unsigned.compare(nameIndex, other.nameIndex); + } + return Unsigned.compare(typeIndex, other.typeIndex); // should always be 0 + } + + public void writeTo(Dex.Section out) { + out.writeUnsignedShort(declaringClassIndex); + out.writeUnsignedShort(typeIndex); + out.writeInt(nameIndex); + } + + @Override public String toString() { + if (dex == null) { + return declaringClassIndex + " " + typeIndex + " " + nameIndex; + } + return dex.typeNames().get(typeIndex) + "." + dex.strings().get(nameIndex); + } +} diff --git a/dexlib/src/main/java/com/android/dex/Leb128.java b/dexlib/src/main/java/com/android/dex/Leb128.java new file mode 100644 index 000000000..e4ca5002d --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/Leb128.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.ByteInput; +import com.android.dex.util.ByteOutput; + +/** + * Reads and writes DWARFv3 LEB 128 signed and unsigned integers. See DWARF v3 + * section 7.6. + */ +public final class Leb128 { + private Leb128() { + } + + /** + * Gets the number of bytes in the unsigned LEB128 encoding of the + * given value. + * + * @param value the value in question + * @return its write size, in bytes + */ + public static int unsignedLeb128Size(int value) { + // TODO: This could be much cleverer. + + int remaining = value >> 7; + int count = 0; + + while (remaining != 0) { + remaining >>= 7; + count++; + } + + return count + 1; + } + + /** + * Reads an signed integer from {@code in}. + */ + public static int readSignedLeb128(ByteInput in) { + int result = 0; + int cur; + int count = 0; + int signBits = -1; + + do { + cur = in.readByte() & 0xff; + result |= (cur & 0x7f) << (count * 7); + signBits <<= 7; + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("invalid LEB128 sequence"); + } + + // Sign extend if appropriate + if (((signBits >> 1) & result) != 0 ) { + result |= signBits; + } + + return result; + } + + /** + * Reads an unsigned integer from {@code in}. + */ + public static int readUnsignedLeb128(ByteInput in) { + int result = 0; + int cur; + int count = 0; + + do { + cur = in.readByte() & 0xff; + result |= (cur & 0x7f) << (count * 7); + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("invalid LEB128 sequence"); + } + + return result; + } + + /** + * Writes {@code value} as an unsigned integer to {@code out}, starting at + * {@code offset}. Returns the number of bytes written. + */ + public static void writeUnsignedLeb128(ByteOutput out, int value) { + int remaining = value >>> 7; + + while (remaining != 0) { + out.writeByte((byte) ((value & 0x7f) | 0x80)); + value = remaining; + remaining >>>= 7; + } + + out.writeByte((byte) (value & 0x7f)); + } + + /** + * Writes {@code value} as a signed integer to {@code out}, starting at + * {@code offset}. Returns the number of bytes written. + */ + public static void writeSignedLeb128(ByteOutput out, int value) { + int remaining = value >> 7; + boolean hasMore = true; + int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1; + + while (hasMore) { + hasMore = (remaining != end) + || ((remaining & 1) != ((value >> 6) & 1)); + + out.writeByte((byte) ((value & 0x7f) | (hasMore ? 0x80 : 0))); + value = remaining; + remaining >>= 7; + } + } +} diff --git a/dexlib/src/main/java/com/android/dex/MethodId.java b/dexlib/src/main/java/com/android/dex/MethodId.java new file mode 100644 index 000000000..e51874026 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/MethodId.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.Unsigned; + +public final class MethodId implements Comparable { + private final Dex dex; + private final int declaringClassIndex; + private final int protoIndex; + private final int nameIndex; + + public MethodId(Dex dex, int declaringClassIndex, int protoIndex, int nameIndex) { + this.dex = dex; + this.declaringClassIndex = declaringClassIndex; + this.protoIndex = protoIndex; + this.nameIndex = nameIndex; + } + + public int getDeclaringClassIndex() { + return declaringClassIndex; + } + + public int getProtoIndex() { + return protoIndex; + } + + public int getNameIndex() { + return nameIndex; + } + + public int compareTo(MethodId other) { + if (declaringClassIndex != other.declaringClassIndex) { + return Unsigned.compare(declaringClassIndex, other.declaringClassIndex); + } + if (nameIndex != other.nameIndex) { + return Unsigned.compare(nameIndex, other.nameIndex); + } + return Unsigned.compare(protoIndex, other.protoIndex); + } + + public void writeTo(Dex.Section out) { + out.writeUnsignedShort(declaringClassIndex); + out.writeUnsignedShort(protoIndex); + out.writeInt(nameIndex); + } + + @Override public String toString() { + if (dex == null) { + return declaringClassIndex + " " + protoIndex + " " + nameIndex; + } + return dex.typeNames().get(declaringClassIndex) + + "." + dex.strings().get(nameIndex) + + dex.readTypeList(dex.protoIds().get(protoIndex).getParametersOffset()); + } +} diff --git a/dexlib/src/main/java/com/android/dex/Mutf8.java b/dexlib/src/main/java/com/android/dex/Mutf8.java new file mode 100644 index 000000000..c64da331b --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/Mutf8.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.ByteInput; +import java.io.UTFDataFormatException; + +/** + * Modified UTF-8 as described in the dex file format spec. + * + *

Derived from libcore's MUTF-8 encoder at java.nio.charset.ModifiedUtf8. + */ +public final class Mutf8 { + private Mutf8() {} + + /** + * Decodes bytes from {@code in} into {@code out} until a delimiter 0x00 is + * encountered. Returns a new string containing the decoded characters. + */ + public static String decode(ByteInput in, char[] out) throws UTFDataFormatException { + int s = 0; + while (true) { + char a = (char) (in.readByte() & 0xff); + if (a == 0) { + return new String(out, 0, s); + } + out[s] = a; + if (a < '\u0080') { + s++; + } else if ((a & 0xe0) == 0xc0) { + int b = in.readByte() & 0xff; + if ((b & 0xC0) != 0x80) { + throw new UTFDataFormatException("bad second byte"); + } + out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F)); + } else if ((a & 0xf0) == 0xe0) { + int b = in.readByte() & 0xff; + int c = in.readByte() & 0xff; + if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) { + throw new UTFDataFormatException("bad second or third byte"); + } + out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)); + } else { + throw new UTFDataFormatException("bad byte"); + } + } + } + + /** + * Returns the number of bytes the modified UTF8 representation of 's' would take. + */ + private static long countBytes(String s, boolean shortLength) throws UTFDataFormatException { + long result = 0; + final int length = s.length(); + for (int i = 0; i < length; ++i) { + char ch = s.charAt(i); + if (ch != 0 && ch <= 127) { // U+0000 uses two bytes. + ++result; + } else if (ch <= 2047) { + result += 2; + } else { + result += 3; + } + if (shortLength && result > 65535) { + throw new UTFDataFormatException("String more than 65535 UTF bytes long"); + } + } + return result; + } + + /** + * Encodes the modified UTF-8 bytes corresponding to {@code s} into {@code + * dst}, starting at {@code offset}. + */ + public static void encode(byte[] dst, int offset, String s) { + final int length = s.length(); + for (int i = 0; i < length; i++) { + char ch = s.charAt(i); + if (ch != 0 && ch <= 127) { // U+0000 uses two bytes. + dst[offset++] = (byte) ch; + } else if (ch <= 2047) { + dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6))); + dst[offset++] = (byte) (0x80 | (0x3f & ch)); + } else { + dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12))); + dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6))); + dst[offset++] = (byte) (0x80 | (0x3f & ch)); + } + } + } + + /** + * Returns an array containing the modified UTF-8 form of {@code s}. + */ + public static byte[] encode(String s) throws UTFDataFormatException { + int utfCount = (int) countBytes(s, true); + byte[] result = new byte[utfCount]; + encode(result, 0, s); + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dex/ProtoId.java b/dexlib/src/main/java/com/android/dex/ProtoId.java new file mode 100644 index 000000000..9d9f484f2 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/ProtoId.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.Unsigned; + +public final class ProtoId implements Comparable { + private final Dex dex; + private final int shortyIndex; + private final int returnTypeIndex; + private final int parametersOffset; + + public ProtoId(Dex dex, int shortyIndex, int returnTypeIndex, int parametersOffset) { + this.dex = dex; + this.shortyIndex = shortyIndex; + this.returnTypeIndex = returnTypeIndex; + this.parametersOffset = parametersOffset; + } + + public int compareTo(ProtoId other) { + if (returnTypeIndex != other.returnTypeIndex) { + return Unsigned.compare(returnTypeIndex, other.returnTypeIndex); + } + return Unsigned.compare(parametersOffset, other.parametersOffset); + } + + public int getShortyIndex() { + return shortyIndex; + } + + public int getReturnTypeIndex() { + return returnTypeIndex; + } + + public int getParametersOffset() { + return parametersOffset; + } + + public void writeTo(Dex.Section out) { + out.writeInt(shortyIndex); + out.writeInt(returnTypeIndex); + out.writeInt(parametersOffset); + } + + @Override public String toString() { + if (dex == null) { + return shortyIndex + " " + returnTypeIndex + " " + parametersOffset; + } + + return dex.strings().get(shortyIndex) + + ": " + dex.typeNames().get(returnTypeIndex) + + " " + dex.readTypeList(parametersOffset); + } +} diff --git a/dexlib/src/main/java/com/android/dex/SizeOf.java b/dexlib/src/main/java/com/android/dex/SizeOf.java new file mode 100644 index 000000000..65fab565b --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/SizeOf.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +public final class SizeOf { + private SizeOf() {} + + public static final int UBYTE = 1; + public static final int USHORT = 2; + public static final int UINT = 4; + + public static final int SIGNATURE = UBYTE * 20; + + /** + * magic ubyte[8] + * checksum uint + * signature ubyte[20] + * file_size uint + * header_size uint + * endian_tag uint + * link_size uint + * link_off uint + * map_off uint + * string_ids_size uint + * string_ids_off uint + * type_ids_size uint + * type_ids_off uint + * proto_ids_size uint + * proto_ids_off uint + * field_ids_size uint + * field_ids_off uint + * method_ids_size uint + * method_ids_off uint + * class_defs_size uint + * class_defs_off uint + * data_size uint + * data_off uint + */ + public static final int HEADER_ITEM = (8 * UBYTE) + UINT + SIGNATURE + (20 * UINT); // 0x70 + + /** + * string_data_off uint + */ + public static final int STRING_ID_ITEM = UINT; + + /** + * descriptor_idx uint + */ + public static final int TYPE_ID_ITEM = UINT; + + /** + * type_idx ushort + */ + public static final int TYPE_ITEM = USHORT; + + /** + * shorty_idx uint + * return_type_idx uint + * return_type_idx uint + */ + public static final int PROTO_ID_ITEM = UINT + UINT + UINT; + + /** + * class_idx ushort + * type_idx/proto_idx ushort + * name_idx uint + */ + public static final int MEMBER_ID_ITEM = USHORT + USHORT + UINT; + + /** + * class_idx uint + * access_flags uint + * superclass_idx uint + * interfaces_off uint + * source_file_idx uint + * annotations_off uint + * class_data_off uint + * static_values_off uint + */ + public static final int CLASS_DEF_ITEM = 8 * UINT; + + /** + * type ushort + * unused ushort + * size uint + * offset uint + */ + public static final int MAP_ITEM = USHORT + USHORT + UINT + UINT; + + /** + * start_addr uint + * insn_count ushort + * handler_off ushort + */ + public static final int TRY_ITEM = UINT + USHORT + USHORT; +} diff --git a/dexlib/src/main/java/com/android/dex/TableOfContents.java b/dexlib/src/main/java/com/android/dex/TableOfContents.java new file mode 100644 index 000000000..b33a7490c --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/TableOfContents.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + +/** + * The file header and map. + */ +public final class TableOfContents { + + /* + * TODO: factor out ID constants. + */ + + public final Section header = new Section(0x0000); + public final Section stringIds = new Section(0x0001); + public final Section typeIds = new Section(0x0002); + public final Section protoIds = new Section(0x0003); + public final Section fieldIds = new Section(0x0004); + public final Section methodIds = new Section(0x0005); + public final Section classDefs = new Section(0x0006); + public final Section callSiteIds = new Section(0x0007); + public final Section methodHandles = new Section(0x0008); + public final Section mapList = new Section(0x1000); + public final Section typeLists = new Section(0x1001); + public final Section annotationSetRefLists = new Section(0x1002); + public final Section annotationSets = new Section(0x1003); + public final Section classDatas = new Section(0x2000); + public final Section codes = new Section(0x2001); + public final Section stringDatas = new Section(0x2002); + public final Section debugInfos = new Section(0x2003); + public final Section annotations = new Section(0x2004); + public final Section encodedArrays = new Section(0x2005); + public final Section annotationsDirectories = new Section(0x2006); + public final Section[] sections = { + header, stringIds, typeIds, protoIds, fieldIds, methodIds, classDefs, mapList, callSiteIds, + methodHandles, typeLists, annotationSetRefLists, annotationSets, classDatas, codes, + stringDatas, debugInfos, annotations, encodedArrays, annotationsDirectories + }; + + public int apiLevel; + public int checksum; + public byte[] signature; + public int fileSize; + public int linkSize; + public int linkOff; + public int dataSize; + public int dataOff; + + public TableOfContents() { + signature = new byte[20]; + } + + public void readFrom(Dex dex) throws IOException { + readHeader(dex.open(0)); + readMap(dex.open(mapList.off)); + computeSizesFromOffsets(); + } + + private void readHeader(Dex.Section headerIn) throws UnsupportedEncodingException { + byte[] magic = headerIn.readByteArray(8); + + if (!DexFormat.isSupportedDexMagic(magic)) { + String msg = + String.format("Unexpected magic: [0x%02x, 0x%02x, 0x%02x, 0x%02x, " + + "0x%02x, 0x%02x, 0x%02x, 0x%02x]", + magic[0], magic[1], magic[2], magic[3], + magic[4], magic[5], magic[6], magic[7]); + throw new DexException(msg); + } + + apiLevel = DexFormat.magicToApi(magic); + checksum = headerIn.readInt(); + signature = headerIn.readByteArray(20); + fileSize = headerIn.readInt(); + int headerSize = headerIn.readInt(); + if (headerSize != SizeOf.HEADER_ITEM) { + throw new DexException("Unexpected header: 0x" + Integer.toHexString(headerSize)); + } + int endianTag = headerIn.readInt(); + if (endianTag != DexFormat.ENDIAN_TAG) { + throw new DexException("Unexpected endian tag: 0x" + Integer.toHexString(endianTag)); + } + linkSize = headerIn.readInt(); + linkOff = headerIn.readInt(); + mapList.off = headerIn.readInt(); + if (mapList.off == 0) { + throw new DexException("Cannot merge dex files that do not contain a map"); + } + stringIds.size = headerIn.readInt(); + stringIds.off = headerIn.readInt(); + typeIds.size = headerIn.readInt(); + typeIds.off = headerIn.readInt(); + protoIds.size = headerIn.readInt(); + protoIds.off = headerIn.readInt(); + fieldIds.size = headerIn.readInt(); + fieldIds.off = headerIn.readInt(); + methodIds.size = headerIn.readInt(); + methodIds.off = headerIn.readInt(); + classDefs.size = headerIn.readInt(); + classDefs.off = headerIn.readInt(); + dataSize = headerIn.readInt(); + dataOff = headerIn.readInt(); + } + + private void readMap(Dex.Section in) throws IOException { + int mapSize = in.readInt(); + Section previous = null; + for (int i = 0; i < mapSize; i++) { + short type = in.readShort(); + in.readShort(); // unused + Section section = getSection(type); + int size = in.readInt(); + int offset = in.readInt(); + + if ((section.size != 0 && section.size != size) + || (section.off != -1 && section.off != offset)) { + throw new DexException("Unexpected map value for 0x" + Integer.toHexString(type)); + } + + section.size = size; + section.off = offset; + + if (previous != null && previous.off > section.off) { + throw new DexException("Map is unsorted at " + previous + ", " + section); + } + + previous = section; + } + Arrays.sort(sections); + } + + public void computeSizesFromOffsets() { + int end = dataOff + dataSize; + for (int i = sections.length - 1; i >= 0; i--) { + Section section = sections[i]; + if (section.off == -1) { + continue; + } + if (section.off > end) { + throw new DexException("Map is unsorted at " + section); + } + section.byteCount = end - section.off; + end = section.off; + } + } + + private Section getSection(short type) { + for (Section section : sections) { + if (section.type == type) { + return section; + } + } + throw new IllegalArgumentException("No such map item: " + type); + } + + public void writeHeader(Dex.Section out, int api) throws IOException { + out.write(DexFormat.apiToMagic(api).getBytes("UTF-8")); + out.writeInt(checksum); + out.write(signature); + out.writeInt(fileSize); + out.writeInt(SizeOf.HEADER_ITEM); + out.writeInt(DexFormat.ENDIAN_TAG); + out.writeInt(linkSize); + out.writeInt(linkOff); + out.writeInt(mapList.off); + out.writeInt(stringIds.size); + out.writeInt(stringIds.off); + out.writeInt(typeIds.size); + out.writeInt(typeIds.off); + out.writeInt(protoIds.size); + out.writeInt(protoIds.off); + out.writeInt(fieldIds.size); + out.writeInt(fieldIds.off); + out.writeInt(methodIds.size); + out.writeInt(methodIds.off); + out.writeInt(classDefs.size); + out.writeInt(classDefs.off); + out.writeInt(dataSize); + out.writeInt(dataOff); + } + + public void writeMap(Dex.Section out) throws IOException { + int count = 0; + for (Section section : sections) { + if (section.exists()) { + count++; + } + } + + out.writeInt(count); + for (Section section : sections) { + if (section.exists()) { + out.writeShort(section.type); + out.writeShort((short) 0); + out.writeInt(section.size); + out.writeInt(section.off); + } + } + } + + public static class Section implements Comparable

{ + public final short type; + public int size = 0; + public int off = -1; + public int byteCount = 0; + + public Section(int type) { + this.type = (short) type; + } + + public boolean exists() { + return size > 0; + } + + public int compareTo(Section section) { + if (off != section.off) { + return off < section.off ? -1 : 1; + } + return 0; + } + + @Override public String toString() { + return String.format("Section[type=%#x,off=%#x,size=%#x]", type, off, size); + } + } +} diff --git a/dexlib/src/main/java/com/android/dex/TypeList.java b/dexlib/src/main/java/com/android/dex/TypeList.java new file mode 100644 index 000000000..123e82c9a --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/TypeList.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex; + +import com.android.dex.util.Unsigned; + +public final class TypeList implements Comparable { + + public static final TypeList EMPTY = new TypeList(null, Dex.EMPTY_SHORT_ARRAY); + + private final Dex dex; + private final short[] types; + + public TypeList(Dex dex, short[] types) { + this.dex = dex; + this.types = types; + } + + public short[] getTypes() { + return types; + } + + @Override public int compareTo(TypeList other) { + for (int i = 0; i < types.length && i < other.types.length; i++) { + if (types[i] != other.types[i]) { + return Unsigned.compare(types[i], other.types[i]); + } + } + return Unsigned.compare(types.length, other.types.length); + } + + @Override public String toString() { + StringBuilder result = new StringBuilder(); + result.append("("); + for (int i = 0, typesLength = types.length; i < typesLength; i++) { + result.append(dex != null ? dex.typeNames().get(types[i]) : types[i]); + } + result.append(")"); + return result.toString(); + } +} diff --git a/dexlib/src/main/java/com/android/dex/util/ByteArrayByteInput.java b/dexlib/src/main/java/com/android/dex/util/ByteArrayByteInput.java new file mode 100644 index 000000000..889a936c5 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/util/ByteArrayByteInput.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex.util; + +public final class ByteArrayByteInput implements ByteInput { + + private final byte[] bytes; + private int position; + + public ByteArrayByteInput(byte... bytes) { + this.bytes = bytes; + } + + @Override public byte readByte() { + return bytes[position++]; + } +} diff --git a/dexlib/src/main/java/com/android/dex/util/ByteInput.java b/dexlib/src/main/java/com/android/dex/util/ByteInput.java new file mode 100644 index 000000000..f1a719614 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/util/ByteInput.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex.util; + +/** + * A byte source. + */ +public interface ByteInput { + + /** + * Returns a byte. + * + * @throws IndexOutOfBoundsException if all bytes have been read. + */ + byte readByte(); +} diff --git a/dexlib/src/main/java/com/android/dex/util/ByteOutput.java b/dexlib/src/main/java/com/android/dex/util/ByteOutput.java new file mode 100644 index 000000000..eb77040ec --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/util/ByteOutput.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex.util; + +/** + * A byte sink. + */ +public interface ByteOutput { + + /** + * Writes a byte. + * + * @throws IndexOutOfBoundsException if all bytes have been written. + */ + void writeByte(int i); +} diff --git a/dexlib/src/main/java/com/android/dex/util/ExceptionWithContext.java b/dexlib/src/main/java/com/android/dex/util/ExceptionWithContext.java new file mode 100644 index 000000000..5dfd95474 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/util/ExceptionWithContext.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex.util; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * Exception which carries around structured context. + */ +public class ExceptionWithContext extends RuntimeException { + /** {@code non-null;} human-oriented context of the exception */ + private StringBuffer context; + + /** + * Augments the given exception with the given context, and return the + * result. The result is either the given exception if it was an + * {@link ExceptionWithContext}, or a newly-constructed exception if it + * was not. + * + * @param ex {@code non-null;} the exception to augment + * @param str {@code non-null;} context to add + * @return {@code non-null;} an appropriate instance + */ + public static ExceptionWithContext withContext(Throwable ex, String str) { + ExceptionWithContext ewc; + + if (ex instanceof ExceptionWithContext) { + ewc = (ExceptionWithContext) ex; + } else { + ewc = new ExceptionWithContext(ex); + } + + ewc.addContext(str); + return ewc; + } + + /** + * Constructs an instance. + * + * @param message human-oriented message + */ + public ExceptionWithContext(String message) { + this(message, null); + } + + /** + * Constructs an instance. + * + * @param cause {@code null-ok;} exception that caused this one + */ + public ExceptionWithContext(Throwable cause) { + this(null, cause); + } + + /** + * Constructs an instance. + * + * @param message human-oriented message + * @param cause {@code null-ok;} exception that caused this one + */ + public ExceptionWithContext(String message, Throwable cause) { + super((message != null) ? message : + (cause != null) ? cause.getMessage() : null, + cause); + + if (cause instanceof ExceptionWithContext) { + String ctx = ((ExceptionWithContext) cause).context.toString(); + context = new StringBuffer(ctx.length() + 200); + context.append(ctx); + } else { + context = new StringBuffer(200); + } + } + + /** {@inheritDoc} */ + @Override + public void printStackTrace(PrintStream out) { + super.printStackTrace(out); + out.println(context); + } + + /** {@inheritDoc} */ + @Override + public void printStackTrace(PrintWriter out) { + super.printStackTrace(out); + out.println(context); + } + + /** + * Adds a line of context to this instance. + * + * @param str {@code non-null;} new context + */ + public void addContext(String str) { + if (str == null) { + throw new NullPointerException("str == null"); + } + + context.append(str); + if (!str.endsWith("\n")) { + context.append('\n'); + } + } + + /** + * Gets the context. + * + * @return {@code non-null;} the context + */ + public String getContext() { + return context.toString(); + } + + /** + * Prints the message and context. + * + * @param out {@code non-null;} where to print to + */ + public void printContext(PrintStream out) { + out.println(getMessage()); + out.print(context); + } + + /** + * Prints the message and context. + * + * @param out {@code non-null;} where to print to + */ + public void printContext(PrintWriter out) { + out.println(getMessage()); + out.print(context); + } +} diff --git a/dexlib/src/main/java/com/android/dex/util/FileUtils.java b/dexlib/src/main/java/com/android/dex/util/FileUtils.java new file mode 100644 index 000000000..4cea95c59 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/util/FileUtils.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * File I/O utilities. + */ +public final class FileUtils { + private FileUtils() { + } + + /** + * Reads the named file, translating {@link IOException} to a + * {@link RuntimeException} of some sort. + * + * @param fileName {@code non-null;} name of the file to read + * @return {@code non-null;} contents of the file + */ + public static byte[] readFile(String fileName) { + File file = new File(fileName); + return readFile(file); + } + + /** + * Reads the given file, translating {@link IOException} to a + * {@link RuntimeException} of some sort. + * + * @param file {@code non-null;} the file to read + * @return {@code non-null;} contents of the file + */ + public static byte[] readFile(File file) { + if (!file.exists()) { + throw new RuntimeException(file + ": file not found"); + } + + if (!file.isFile()) { + throw new RuntimeException(file + ": not a file"); + } + + if (!file.canRead()) { + throw new RuntimeException(file + ": file not readable"); + } + + long longLength = file.length(); + int length = (int) longLength; + if (length != longLength) { + throw new RuntimeException(file + ": file too long"); + } + + byte[] result = new byte[length]; + + try { + FileInputStream in = new FileInputStream(file); + int at = 0; + while (length > 0) { + int amt = in.read(result, at, length); + if (amt == -1) { + throw new RuntimeException(file + ": unexpected EOF"); + } + at += amt; + length -= amt; + } + in.close(); + } catch (IOException ex) { + throw new RuntimeException(file + ": trouble reading", ex); + } + + return result; + } + + /** + * Returns true if {@code fileName} names a .zip, .jar, or .apk. + */ + public static boolean hasArchiveSuffix(String fileName) { + return fileName.endsWith(".zip") + || fileName.endsWith(".jar") + || fileName.endsWith(".apk"); + } +} diff --git a/dexlib/src/main/java/com/android/dex/util/Unsigned.java b/dexlib/src/main/java/com/android/dex/util/Unsigned.java new file mode 100644 index 000000000..cb50d0a40 --- /dev/null +++ b/dexlib/src/main/java/com/android/dex/util/Unsigned.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dex.util; + +/** + * Unsigned arithmetic over Java's signed types. + */ +public final class Unsigned { + private Unsigned() {} + + public static int compare(short ushortA, short ushortB) { + if (ushortA == ushortB) { + return 0; + } + int a = ushortA & 0xFFFF; + int b = ushortB & 0xFFFF; + return a < b ? -1 : 1; + } + + public static int compare(int uintA, int uintB) { + if (uintA == uintB) { + return 0; + } + long a = uintA & 0xFFFFFFFFL; + long b = uintB & 0xFFFFFFFFL; + return a < b ? -1 : 1; + } +} diff --git a/dexlib/src/main/java/com/android/dx/Version.java b/dexlib/src/main/java/com/android/dx/Version.java new file mode 100644 index 000000000..88225e636 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/Version.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx; + +/** + * Version number for dx. + */ +public class Version { + /** {@code non-null;} version string */ + public static final String VERSION = "1.13"; +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttAnnotationDefault.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttAnnotationDefault.java new file mode 100644 index 000000000..fe0b3abfe --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttAnnotationDefault.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.cst.Constant; + +/** + * Attribute class for {@code AnnotationDefault} attributes. + */ +public final class AttAnnotationDefault extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "AnnotationDefault"; + + /** {@code non-null;} the annotation default value */ + private final Constant value; + + /** {@code >= 0;} attribute data length in the original classfile (not + * including the attribute header) */ + private final int byteLength; + + /** + * Constructs an instance. + * + * @param value {@code non-null;} the annotation default value + * @param byteLength {@code >= 0;} attribute data length in the original + * classfile (not including the attribute header) + */ + public AttAnnotationDefault(Constant value, int byteLength) { + super(ATTRIBUTE_NAME); + + if (value == null) { + throw new NullPointerException("value == null"); + } + + this.value = value; + this.byteLength = byteLength; + } + + /** {@inheritDoc} */ + public int byteLength() { + // Add six for the standard attribute header. + return byteLength + 6; + } + + /** + * Gets the annotation default value. + * + * @return {@code non-null;} the value + */ + public Constant getValue() { + return value; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttCode.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttCode.java new file mode 100644 index 000000000..8d34c69e9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttCode.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.cf.code.ByteCatchList; +import com.android.dx.cf.code.BytecodeArray; +import com.android.dx.cf.iface.AttributeList; +import com.android.dx.util.MutabilityException; + +/** + * Attribute class for standard {@code Code} attributes. + */ +public final class AttCode extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "Code"; + + /** {@code >= 0;} the stack size */ + private final int maxStack; + + /** {@code >= 0;} the number of locals */ + private final int maxLocals; + + /** {@code non-null;} array containing the bytecode per se */ + private final BytecodeArray code; + + /** {@code non-null;} the exception table */ + private final ByteCatchList catches; + + /** {@code non-null;} the associated list of attributes */ + private final AttributeList attributes; + + /** + * Constructs an instance. + * + * @param maxStack {@code >= 0;} the stack size + * @param maxLocals {@code >= 0;} the number of locals + * @param code {@code non-null;} array containing the bytecode per se + * @param catches {@code non-null;} the exception table + * @param attributes {@code non-null;} the associated list of attributes + */ + public AttCode(int maxStack, int maxLocals, BytecodeArray code, + ByteCatchList catches, AttributeList attributes) { + super(ATTRIBUTE_NAME); + + if (maxStack < 0) { + throw new IllegalArgumentException("maxStack < 0"); + } + + if (maxLocals < 0) { + throw new IllegalArgumentException("maxLocals < 0"); + } + + if (code == null) { + throw new NullPointerException("code == null"); + } + + try { + if (catches.isMutable()) { + throw new MutabilityException("catches.isMutable()"); + } + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("catches == null"); + } + + try { + if (attributes.isMutable()) { + throw new MutabilityException("attributes.isMutable()"); + } + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("attributes == null"); + } + + this.maxStack = maxStack; + this.maxLocals = maxLocals; + this.code = code; + this.catches = catches; + this.attributes = attributes; + } + + public int byteLength() { + return 10 + code.byteLength() + catches.byteLength() + + attributes.byteLength(); + } + + /** + * Gets the maximum stack size. + * + * @return {@code >= 0;} the maximum stack size + */ + public int getMaxStack() { + return maxStack; + } + + /** + * Gets the number of locals. + * + * @return {@code >= 0;} the number of locals + */ + public int getMaxLocals() { + return maxLocals; + } + + /** + * Gets the bytecode array. + * + * @return {@code non-null;} the bytecode array + */ + public BytecodeArray getCode() { + return code; + } + + /** + * Gets the exception table. + * + * @return {@code non-null;} the exception table + */ + public ByteCatchList getCatches() { + return catches; + } + + /** + * Gets the associated attribute list. + * + * @return {@code non-null;} the attribute list + */ + public AttributeList getAttributes() { + return attributes; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttConstantValue.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttConstantValue.java new file mode 100644 index 000000000..aa6d1b315 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttConstantValue.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.cst.CstDouble; +import com.android.dx.rop.cst.CstFloat; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstLong; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.TypedConstant; + +/** + * Attribute class for standard {@code ConstantValue} attributes. + */ +public final class AttConstantValue extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "ConstantValue"; + + /** {@code non-null;} the constant value */ + private final TypedConstant constantValue; + + /** + * Constructs an instance. + * + * @param constantValue {@code non-null;} the constant value, which must + * be an instance of one of: {@code CstString}, + * {@code CstInteger}, {@code CstLong}, + * {@code CstFloat}, or {@code CstDouble} + */ + public AttConstantValue(TypedConstant constantValue) { + super(ATTRIBUTE_NAME); + + if (!((constantValue instanceof CstString) || + (constantValue instanceof CstInteger) || + (constantValue instanceof CstLong) || + (constantValue instanceof CstFloat) || + (constantValue instanceof CstDouble))) { + if (constantValue == null) { + throw new NullPointerException("constantValue == null"); + } + throw new IllegalArgumentException("bad type for constantValue"); + } + + this.constantValue = constantValue; + } + + /** {@inheritDoc} */ + public int byteLength() { + return 8; + } + + /** + * Gets the constant value of this instance. The returned value + * is an instance of one of: {@code CstString}, + * {@code CstInteger}, {@code CstLong}, + * {@code CstFloat}, or {@code CstDouble}. + * + * @return {@code non-null;} the constant value + */ + public TypedConstant getConstantValue() { + return constantValue; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttDeprecated.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttDeprecated.java new file mode 100644 index 000000000..d440aae71 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttDeprecated.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +/** + * Attribute class for standard {@code Deprecated} attributes. + */ +public final class AttDeprecated extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "Deprecated"; + + /** + * Constructs an instance. + */ + public AttDeprecated() { + super(ATTRIBUTE_NAME); + } + + /** {@inheritDoc} */ + public int byteLength() { + return 6; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttEnclosingMethod.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttEnclosingMethod.java new file mode 100644 index 000000000..6717e15e9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttEnclosingMethod.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstType; + +/** + * Attribute class for standards-track {@code EnclosingMethod} + * attributes. + */ +public final class AttEnclosingMethod extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "EnclosingMethod"; + + /** {@code non-null;} the innermost enclosing class */ + private final CstType type; + + /** {@code null-ok;} the name-and-type of the innermost enclosing method, if any */ + private final CstNat method; + + /** + * Constructs an instance. + * + * @param type {@code non-null;} the innermost enclosing class + * @param method {@code null-ok;} the name-and-type of the innermost enclosing + * method, if any + */ + public AttEnclosingMethod(CstType type, CstNat method) { + super(ATTRIBUTE_NAME); + + if (type == null) { + throw new NullPointerException("type == null"); + } + + this.type = type; + this.method = method; + } + + /** {@inheritDoc} */ + public int byteLength() { + return 10; + } + + /** + * Gets the innermost enclosing class. + * + * @return {@code non-null;} the innermost enclosing class + */ + public CstType getEnclosingClass() { + return type; + } + + /** + * Gets the name-and-type of the innermost enclosing method, if + * any. + * + * @return {@code null-ok;} the name-and-type of the innermost enclosing + * method, if any + */ + public CstNat getMethod() { + return method; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttExceptions.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttExceptions.java new file mode 100644 index 000000000..a17e009a8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttExceptions.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.MutabilityException; + +/** + * Attribute class for standard {@code Exceptions} attributes. + */ +public final class AttExceptions extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "Exceptions"; + + /** {@code non-null;} list of exception classes */ + private final TypeList exceptions; + + /** + * Constructs an instance. + * + * @param exceptions {@code non-null;} list of classes, presumed but not + * verified to be subclasses of {@code Throwable} + */ + public AttExceptions(TypeList exceptions) { + super(ATTRIBUTE_NAME); + + try { + if (exceptions.isMutable()) { + throw new MutabilityException("exceptions.isMutable()"); + } + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("exceptions == null"); + } + + this.exceptions = exceptions; + } + + /** {@inheritDoc} */ + public int byteLength() { + return 8 + exceptions.size() * 2; + } + + /** + * Gets the list of classes associated with this instance. In + * general, these classes are not pre-verified to be subclasses of + * {@code Throwable}. + * + * @return {@code non-null;} the list of classes + */ + public TypeList getExceptions() { + return exceptions; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttInnerClasses.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttInnerClasses.java new file mode 100644 index 000000000..77a4b087a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttInnerClasses.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.util.MutabilityException; + +/** + * Attribute class for standard {@code InnerClasses} attributes. + */ +public final class AttInnerClasses extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "InnerClasses"; + + /** {@code non-null;} list of inner class entries */ + private final InnerClassList innerClasses; + + /** + * Constructs an instance. + * + * @param innerClasses {@code non-null;} list of inner class entries + */ + public AttInnerClasses(InnerClassList innerClasses) { + super(ATTRIBUTE_NAME); + + try { + if (innerClasses.isMutable()) { + throw new MutabilityException("innerClasses.isMutable()"); + } + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("innerClasses == null"); + } + + this.innerClasses = innerClasses; + } + + /** {@inheritDoc} */ + public int byteLength() { + return 8 + innerClasses.size() * 8; + } + + /** + * Gets the list of "inner class" entries associated with this instance. + * + * @return {@code non-null;} the list + */ + public InnerClassList getInnerClasses() { + return innerClasses; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttLineNumberTable.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttLineNumberTable.java new file mode 100644 index 000000000..5eac8cbec --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttLineNumberTable.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.cf.code.LineNumberList; +import com.android.dx.util.MutabilityException; + +/** + * Attribute class for standard {@code LineNumberTable} attributes. + */ +public final class AttLineNumberTable extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "LineNumberTable"; + + /** {@code non-null;} list of line number entries */ + private final LineNumberList lineNumbers; + + /** + * Constructs an instance. + * + * @param lineNumbers {@code non-null;} list of line number entries + */ + public AttLineNumberTable(LineNumberList lineNumbers) { + super(ATTRIBUTE_NAME); + + try { + if (lineNumbers.isMutable()) { + throw new MutabilityException("lineNumbers.isMutable()"); + } + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("lineNumbers == null"); + } + + this.lineNumbers = lineNumbers; + } + + /** {@inheritDoc} */ + public int byteLength() { + return 8 + 4 * lineNumbers.size(); + } + + /** + * Gets the list of "line number" entries associated with this instance. + * + * @return {@code non-null;} the list + */ + public LineNumberList getLineNumbers() { + return lineNumbers; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttLocalVariableTable.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttLocalVariableTable.java new file mode 100644 index 000000000..1d2b4aa01 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttLocalVariableTable.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.cf.code.LocalVariableList; + +/** + * Attribute class for standard {@code LocalVariableTable} attributes. + */ +public final class AttLocalVariableTable extends BaseLocalVariables { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "LocalVariableTable"; + + /** + * Constructs an instance. + * + * @param localVariables {@code non-null;} list of local variable entries + */ + public AttLocalVariableTable(LocalVariableList localVariables) { + super(ATTRIBUTE_NAME, localVariables); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttLocalVariableTypeTable.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttLocalVariableTypeTable.java new file mode 100644 index 000000000..2520bf604 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttLocalVariableTypeTable.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.cf.code.LocalVariableList; + +/** + * Attribute class for standard {@code LocalVariableTypeTable} attributes. + */ +public final class AttLocalVariableTypeTable extends BaseLocalVariables { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "LocalVariableTypeTable"; + + /** + * Constructs an instance. + * + * @param localVariables {@code non-null;} list of local variable entries + */ + public AttLocalVariableTypeTable(LocalVariableList localVariables) { + super(ATTRIBUTE_NAME, localVariables); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeInvisibleAnnotations.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeInvisibleAnnotations.java new file mode 100644 index 000000000..d3afe277f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeInvisibleAnnotations.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.annotation.Annotations; + +/** + * Attribute class for standard {@code RuntimeInvisibleAnnotations} + * attributes. + */ +public final class AttRuntimeInvisibleAnnotations extends BaseAnnotations { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "RuntimeInvisibleAnnotations"; + + /** + * Constructs an instance. + * + * @param annotations {@code non-null;} the list of annotations + * @param byteLength {@code >= 0;} attribute data length in the original + * classfile (not including the attribute header) + */ + public AttRuntimeInvisibleAnnotations(Annotations annotations, + int byteLength) { + super(ATTRIBUTE_NAME, annotations, byteLength); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeInvisibleParameterAnnotations.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeInvisibleParameterAnnotations.java new file mode 100644 index 000000000..c9c513694 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeInvisibleParameterAnnotations.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.annotation.AnnotationsList; + +/** + * Attribute class for standard + * {@code RuntimeInvisibleParameterAnnotations} attributes. + */ +public final class AttRuntimeInvisibleParameterAnnotations + extends BaseParameterAnnotations { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = + "RuntimeInvisibleParameterAnnotations"; + + /** + * Constructs an instance. + * + * @param parameterAnnotations {@code non-null;} the parameter annotations + * @param byteLength {@code >= 0;} attribute data length in the original + * classfile (not including the attribute header) + */ + public AttRuntimeInvisibleParameterAnnotations( + AnnotationsList parameterAnnotations, int byteLength) { + super(ATTRIBUTE_NAME, parameterAnnotations, byteLength); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeVisibleAnnotations.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeVisibleAnnotations.java new file mode 100644 index 000000000..a6a640d53 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeVisibleAnnotations.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.annotation.Annotations; + +/** + * Attribute class for standard {@code RuntimeVisibleAnnotations} + * attributes. + */ +public final class AttRuntimeVisibleAnnotations extends BaseAnnotations { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "RuntimeVisibleAnnotations"; + + /** + * Constructs an instance. + * + * @param annotations {@code non-null;} the list of annotations + * @param byteLength {@code >= 0;} attribute data length in the original + * classfile (not including the attribute header) + */ + public AttRuntimeVisibleAnnotations(Annotations annotations, + int byteLength) { + super(ATTRIBUTE_NAME, annotations, byteLength); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeVisibleParameterAnnotations.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeVisibleParameterAnnotations.java new file mode 100644 index 000000000..177eb4c9b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttRuntimeVisibleParameterAnnotations.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.annotation.AnnotationsList; + +/** + * Attribute class for standard {@code RuntimeVisibleParameterAnnotations} + * attributes. + */ +public final class AttRuntimeVisibleParameterAnnotations + extends BaseParameterAnnotations { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = + "RuntimeVisibleParameterAnnotations"; + + /** + * Constructs an instance. + * + * @param annotations {@code non-null;} the parameter annotations + * @param byteLength {@code >= 0;} attribute data length in the original + * classfile (not including the attribute header) + */ + public AttRuntimeVisibleParameterAnnotations( + AnnotationsList annotations, int byteLength) { + super(ATTRIBUTE_NAME, annotations, byteLength); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttSignature.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttSignature.java new file mode 100644 index 000000000..52def9c8b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttSignature.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.cst.CstString; + +/** + * Attribute class for standards-track {@code Signature} attributes. + */ +public final class AttSignature extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "Signature"; + + /** {@code non-null;} the signature string */ + private final CstString signature; + + /** + * Constructs an instance. + * + * @param signature {@code non-null;} the signature string + */ + public AttSignature(CstString signature) { + super(ATTRIBUTE_NAME); + + if (signature == null) { + throw new NullPointerException("signature == null"); + } + + this.signature = signature; + } + + /** {@inheritDoc} */ + public int byteLength() { + return 8; + } + + /** + * Gets the signature string. + * + * @return {@code non-null;} the signature string + */ + public CstString getSignature() { + return signature; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttSourceDebugExtension.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttSourceDebugExtension.java new file mode 100644 index 000000000..3dd4fecb1 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttSourceDebugExtension.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.cst.CstString; + +/** + * Attribute class for standard {@code SourceDebugExtension} attributes. + */ +public final class AttSourceDebugExtension extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "SourceDebugExtension"; + + /** {@code non-null;} Contents of SMAP */ + private final CstString smapString; + + /** + * Constructs an instance. + * + * @param smapString {@code non-null;} the SMAP data from the class file. + */ + public AttSourceDebugExtension(CstString smapString) { + super(ATTRIBUTE_NAME); + + if (smapString == null) { + throw new NullPointerException("smapString == null"); + } + + this.smapString = smapString; + } + + /** {@inheritDoc} */ + @Override + public int byteLength() { + // Add 6 for the standard attribute header: the attribute name + // index (2 bytes) and the attribute length (4 bytes). + return 6 + smapString.getUtf8Size(); + } + + /** + * Gets the SMAP data of this instance. + * + * @return {@code non-null;} the SMAP data. + */ + public CstString getSmapString() { + return smapString; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttSourceFile.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttSourceFile.java new file mode 100644 index 000000000..cc19d2750 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttSourceFile.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.cst.CstString; + +/** + * Attribute class for standard {@code SourceFile} attributes. + */ +public final class AttSourceFile extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "SourceFile"; + + /** {@code non-null;} name of the source file */ + private final CstString sourceFile; + + /** + * Constructs an instance. + * + * @param sourceFile {@code non-null;} the name of the source file + */ + public AttSourceFile(CstString sourceFile) { + super(ATTRIBUTE_NAME); + + if (sourceFile == null) { + throw new NullPointerException("sourceFile == null"); + } + + this.sourceFile = sourceFile; + } + + /** {@inheritDoc} */ + public int byteLength() { + return 8; + } + + /** + * Gets the source file name of this instance. + * + * @return {@code non-null;} the source file + */ + public CstString getSourceFile() { + return sourceFile; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/AttSynthetic.java b/dexlib/src/main/java/com/android/dx/cf/attrib/AttSynthetic.java new file mode 100644 index 000000000..e3841eb86 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/AttSynthetic.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +/** + * Attribute class for standard {@code Synthetic} attributes. + */ +public final class AttSynthetic extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "Synthetic"; + + /** + * Constructs an instance. + */ + public AttSynthetic() { + super(ATTRIBUTE_NAME); + } + + /** {@inheritDoc} */ + public int byteLength() { + return 6; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/BaseAnnotations.java b/dexlib/src/main/java/com/android/dx/cf/attrib/BaseAnnotations.java new file mode 100644 index 000000000..bc138afe7 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/BaseAnnotations.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.util.MutabilityException; + +/** + * Base class for annotations attributes. + */ +public abstract class BaseAnnotations extends BaseAttribute { + /** {@code non-null;} list of annotations */ + private final Annotations annotations; + + /** {@code >= 0;} attribute data length in the original classfile (not + * including the attribute header) */ + private final int byteLength; + + /** + * Constructs an instance. + * + * @param attributeName {@code non-null;} the name of the attribute + * @param annotations {@code non-null;} the list of annotations + * @param byteLength {@code >= 0;} attribute data length in the original + * classfile (not including the attribute header) + */ + public BaseAnnotations(String attributeName, Annotations annotations, + int byteLength) { + super(attributeName); + + try { + if (annotations.isMutable()) { + throw new MutabilityException("annotations.isMutable()"); + } + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("annotations == null"); + } + + this.annotations = annotations; + this.byteLength = byteLength; + } + + /** {@inheritDoc} */ + public final int byteLength() { + // Add six for the standard attribute header. + return byteLength + 6; + } + + /** + * Gets the list of annotations associated with this instance. + * + * @return {@code non-null;} the list + */ + public final Annotations getAnnotations() { + return annotations; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/BaseAttribute.java b/dexlib/src/main/java/com/android/dx/cf/attrib/BaseAttribute.java new file mode 100644 index 000000000..99617250b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/BaseAttribute.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.cf.iface.Attribute; + +/** + * Base implementation of {@link Attribute}, which directly stores + * the attribute name but leaves the rest up to subclasses. + */ +public abstract class BaseAttribute implements Attribute { + /** {@code non-null;} attribute name */ + private final String name; + + /** + * Constructs an instance. + * + * @param name {@code non-null;} attribute name + */ + public BaseAttribute(String name) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + this.name = name; + } + + /** {@inheritDoc} */ + public String getName() { + return name; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/BaseLocalVariables.java b/dexlib/src/main/java/com/android/dx/cf/attrib/BaseLocalVariables.java new file mode 100644 index 000000000..27cd6fb9e --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/BaseLocalVariables.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.cf.code.LocalVariableList; +import com.android.dx.util.MutabilityException; + +/** + * Base attribute class for standard {@code LocalVariableTable} + * and {@code LocalVariableTypeTable} attributes. + */ +public abstract class BaseLocalVariables extends BaseAttribute { + /** {@code non-null;} list of local variable entries */ + private final LocalVariableList localVariables; + + /** + * Constructs an instance. + * + * @param name {@code non-null;} attribute name + * @param localVariables {@code non-null;} list of local variable entries + */ + public BaseLocalVariables(String name, + LocalVariableList localVariables) { + super(name); + + try { + if (localVariables.isMutable()) { + throw new MutabilityException("localVariables.isMutable()"); + } + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("localVariables == null"); + } + + this.localVariables = localVariables; + } + + /** {@inheritDoc} */ + public final int byteLength() { + return 8 + localVariables.size() * 10; + } + + /** + * Gets the list of "local variable" entries associated with this instance. + * + * @return {@code non-null;} the list + */ + public final LocalVariableList getLocalVariables() { + return localVariables; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/BaseParameterAnnotations.java b/dexlib/src/main/java/com/android/dx/cf/attrib/BaseParameterAnnotations.java new file mode 100644 index 000000000..791f8cdfc --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/BaseParameterAnnotations.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.util.MutabilityException; + +/** + * Base class for parameter annotation list attributes. + */ +public abstract class BaseParameterAnnotations extends BaseAttribute { + /** {@code non-null;} list of annotations */ + private final AnnotationsList parameterAnnotations; + + /** {@code >= 0;} attribute data length in the original classfile (not + * including the attribute header) */ + private final int byteLength; + + /** + * Constructs an instance. + * + * @param attributeName {@code non-null;} the name of the attribute + * @param parameterAnnotations {@code non-null;} the annotations + * @param byteLength {@code >= 0;} attribute data length in the original + * classfile (not including the attribute header) + */ + public BaseParameterAnnotations(String attributeName, + AnnotationsList parameterAnnotations, int byteLength) { + super(attributeName); + + try { + if (parameterAnnotations.isMutable()) { + throw new MutabilityException( + "parameterAnnotations.isMutable()"); + } + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("parameterAnnotations == null"); + } + + this.parameterAnnotations = parameterAnnotations; + this.byteLength = byteLength; + } + + /** {@inheritDoc} */ + public final int byteLength() { + // Add six for the standard attribute header. + return byteLength + 6; + } + + /** + * Gets the list of annotation lists associated with this instance. + * + * @return {@code non-null;} the list + */ + public final AnnotationsList getParameterAnnotations() { + return parameterAnnotations; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/InnerClassList.java b/dexlib/src/main/java/com/android/dx/cf/attrib/InnerClassList.java new file mode 100644 index 000000000..830118c5a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/InnerClassList.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.FixedSizeList; + +/** + * List of "inner class" entries, which are the contents of + * {@code InnerClasses} attributes. + */ +public final class InnerClassList extends FixedSizeList { + /** + * Constructs an instance. + * + * @param count the number of elements to be in the list of inner classes + */ + public InnerClassList(int count) { + super(count); + } + + /** + * Gets the indicated item. + * + * @param n {@code >= 0;} which item + * @return {@code null-ok;} the indicated item + */ + public Item get(int n) { + return (Item) get0(n); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which class + * @param innerClass {@code non-null;} class this item refers to + * @param outerClass {@code null-ok;} outer class that this class is a + * member of, if any + * @param innerName {@code null-ok;} original simple name of this class, + * if not anonymous + * @param accessFlags original declared access flags + */ + public void set(int n, CstType innerClass, CstType outerClass, + CstString innerName, int accessFlags) { + set0(n, new Item(innerClass, outerClass, innerName, accessFlags)); + } + + /** + * Item in an inner classes list. + */ + public static class Item { + /** {@code non-null;} class this item refers to */ + private final CstType innerClass; + + /** {@code null-ok;} outer class that this class is a member of, if any */ + private final CstType outerClass; + + /** {@code null-ok;} original simple name of this class, if not anonymous */ + private final CstString innerName; + + /** original declared access flags */ + private final int accessFlags; + + /** + * Constructs an instance. + * + * @param innerClass {@code non-null;} class this item refers to + * @param outerClass {@code null-ok;} outer class that this class is a + * member of, if any + * @param innerName {@code null-ok;} original simple name of this + * class, if not anonymous + * @param accessFlags original declared access flags + */ + public Item(CstType innerClass, CstType outerClass, + CstString innerName, int accessFlags) { + if (innerClass == null) { + throw new NullPointerException("innerClass == null"); + } + + this.innerClass = innerClass; + this.outerClass = outerClass; + this.innerName = innerName; + this.accessFlags = accessFlags; + } + + /** + * Gets the class this item refers to. + * + * @return {@code non-null;} the class + */ + public CstType getInnerClass() { + return innerClass; + } + + /** + * Gets the outer class that this item's class is a member of, if any. + * + * @return {@code null-ok;} the class + */ + public CstType getOuterClass() { + return outerClass; + } + + /** + * Gets the original name of this item's class, if not anonymous. + * + * @return {@code null-ok;} the name + */ + public CstString getInnerName() { + return innerName; + } + + /** + * Gets the original declared access flags. + * + * @return the access flags + */ + public int getAccessFlags() { + return accessFlags; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/RawAttribute.java b/dexlib/src/main/java/com/android/dx/cf/attrib/RawAttribute.java new file mode 100644 index 000000000..e905dd1d0 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/RawAttribute.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.attrib; + +import com.android.dx.rop.cst.ConstantPool; +import com.android.dx.util.ByteArray; + +/** + * Raw attribute, for holding onto attributes that are unrecognized. + */ +public final class RawAttribute extends BaseAttribute { + /** {@code non-null;} attribute data */ + private final ByteArray data; + + /** + * {@code null-ok;} constant pool to use for resolution of cpis in {@link + * #data} + */ + private final ConstantPool pool; + + /** + * Constructs an instance. + * + * @param name {@code non-null;} attribute name + * @param data {@code non-null;} attribute data + * @param pool {@code null-ok;} constant pool to use for cpi resolution + */ + public RawAttribute(String name, ByteArray data, ConstantPool pool) { + super(name); + + if (data == null) { + throw new NullPointerException("data == null"); + } + + this.data = data; + this.pool = pool; + } + + /** + * Constructs an instance from a sub-array of a {@link ByteArray}. + * + * @param name {@code non-null;} attribute name + * @param data {@code non-null;} array containing the attribute data + * @param offset offset in {@code data} to the attribute data + * @param length length of the attribute data, in bytes + * @param pool {@code null-ok;} constant pool to use for cpi resolution + */ + public RawAttribute(String name, ByteArray data, int offset, + int length, ConstantPool pool) { + this(name, data.slice(offset, offset + length), pool); + } + + /** + * Get the raw data of the attribute. + * + * @return {@code non-null;} the data + */ + public ByteArray getData() { + return data; + } + + /** {@inheritDoc} */ + public int byteLength() { + return data.size() + 6; + } + + /** + * Gets the constant pool to use for cpi resolution, if any. It + * presumably came from the class file that this attribute came + * from. + * + * @return {@code null-ok;} the constant pool + */ + public ConstantPool getPool() { + return pool; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/attrib/package.html b/dexlib/src/main/java/com/android/dx/cf/attrib/package.html new file mode 100644 index 000000000..8125079a4 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/attrib/package.html @@ -0,0 +1,11 @@ + +

Implementation of containers and utilities for all the standard Java +attribute types.

+ +

PACKAGES USED: +

    +
  • com.android.dx.cf.iface
  • +
  • com.android.dx.rop.pool
  • +
  • com.android.dx.util
  • +
+ diff --git a/dexlib/src/main/java/com/android/dx/cf/code/BaseMachine.java b/dexlib/src/main/java/com/android/dx/cf/code/BaseMachine.java new file mode 100644 index 000000000..7bff2ff2f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/BaseMachine.java @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import java.util.ArrayList; + +/** + * Base implementation of {@link Machine}. + * + *

Note: For the most part, the documentation for this class + * ignores the distinction between {@link Type} and {@link + * TypeBearer}.

+ */ +public abstract class BaseMachine implements Machine { + /* {@code non-null;} the prototype for the associated method */ + private final Prototype prototype; + + /** {@code non-null;} primary arguments */ + private TypeBearer[] args; + + /** {@code >= 0;} number of primary arguments */ + private int argCount; + + /** {@code null-ok;} type of the operation, if salient */ + private Type auxType; + + /** auxiliary {@code int} argument */ + private int auxInt; + + /** {@code null-ok;} auxiliary constant argument */ + private Constant auxCst; + + /** auxiliary branch target argument */ + private int auxTarget; + + /** {@code null-ok;} auxiliary switch cases argument */ + private SwitchList auxCases; + + /** {@code null-ok;} auxiliary initial value list for newarray */ + private ArrayList auxInitValues; + + /** {@code >= -1;} last local accessed */ + private int localIndex; + + /** specifies if local has info in the local variable table */ + private boolean localInfo; + + /** {@code null-ok;} local target spec, if salient and calculated */ + private RegisterSpec localTarget; + + /** {@code non-null;} results */ + private TypeBearer[] results; + + /** + * {@code >= -1;} count of the results, or {@code -1} if no results + * have been set + */ + private int resultCount; + + /** + * Constructs an instance. + * + * @param prototype {@code non-null;} the prototype for the + * associated method + */ + public BaseMachine(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + this.prototype = prototype; + args = new TypeBearer[10]; + results = new TypeBearer[6]; + clearArgs(); + } + + /** {@inheritDoc} */ + public Prototype getPrototype() { + return prototype; + } + + /** {@inheritDoc} */ + public final void clearArgs() { + argCount = 0; + auxType = null; + auxInt = 0; + auxCst = null; + auxTarget = 0; + auxCases = null; + auxInitValues = null; + localIndex = -1; + localInfo = false; + localTarget = null; + resultCount = -1; + } + + /** {@inheritDoc} */ + public final void popArgs(Frame frame, int count) { + ExecutionStack stack = frame.getStack(); + + clearArgs(); + + if (count > args.length) { + // Grow args, and add a little extra room to grow even more. + args = new TypeBearer[count + 10]; + } + + for (int i = count - 1; i >= 0; i--) { + args[i] = stack.pop(); + } + + argCount = count; + } + + /** {@inheritDoc} */ + public void popArgs(Frame frame, Prototype prototype) { + StdTypeList types = prototype.getParameterTypes(); + int size = types.size(); + + // Use the above method to do the actual popping... + popArgs(frame, size); + + // ...and then verify the popped types. + + for (int i = 0; i < size; i++) { + if (! Merger.isPossiblyAssignableFrom(types.getType(i), args[i])) { + throw new SimException("at stack depth " + (size - 1 - i) + + ", expected type " + types.getType(i).toHuman() + + " but found " + args[i].getType().toHuman()); + } + } + } + + public final void popArgs(Frame frame, Type type) { + // Use the above method to do the actual popping... + popArgs(frame, 1); + + // ...and then verify the popped type. + if (! Merger.isPossiblyAssignableFrom(type, args[0])) { + throw new SimException("expected type " + type.toHuman() + + " but found " + args[0].getType().toHuman()); + } + } + + /** {@inheritDoc} */ + public final void popArgs(Frame frame, Type type1, Type type2) { + // Use the above method to do the actual popping... + popArgs(frame, 2); + + // ...and then verify the popped types. + + if (! Merger.isPossiblyAssignableFrom(type1, args[0])) { + throw new SimException("expected type " + type1.toHuman() + + " but found " + args[0].getType().toHuman()); + } + + if (! Merger.isPossiblyAssignableFrom(type2, args[1])) { + throw new SimException("expected type " + type2.toHuman() + + " but found " + args[1].getType().toHuman()); + } + } + + /** {@inheritDoc} */ + public final void popArgs(Frame frame, Type type1, Type type2, + Type type3) { + // Use the above method to do the actual popping... + popArgs(frame, 3); + + // ...and then verify the popped types. + + if (! Merger.isPossiblyAssignableFrom(type1, args[0])) { + throw new SimException("expected type " + type1.toHuman() + + " but found " + args[0].getType().toHuman()); + } + + if (! Merger.isPossiblyAssignableFrom(type2, args[1])) { + throw new SimException("expected type " + type2.toHuman() + + " but found " + args[1].getType().toHuman()); + } + + if (! Merger.isPossiblyAssignableFrom(type3, args[2])) { + throw new SimException("expected type " + type3.toHuman() + + " but found " + args[2].getType().toHuman()); + } + } + + /** {@inheritDoc} */ + public final void localArg(Frame frame, int idx) { + clearArgs(); + args[0] = frame.getLocals().get(idx); + argCount = 1; + localIndex = idx; + } + + /** {@inheritDoc} */ + public final void localInfo(boolean local) { + localInfo = local; + } + + /** {@inheritDoc} */ + public final void auxType(Type type) { + auxType = type; + } + + /** {@inheritDoc} */ + public final void auxIntArg(int value) { + auxInt = value; + } + + /** {@inheritDoc} */ + public final void auxCstArg(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + auxCst = cst; + } + + /** {@inheritDoc} */ + public final void auxTargetArg(int target) { + auxTarget = target; + } + + /** {@inheritDoc} */ + public final void auxSwitchArg(SwitchList cases) { + if (cases == null) { + throw new NullPointerException("cases == null"); + } + + auxCases = cases; + } + + /** {@inheritDoc} */ + public final void auxInitValues(ArrayList initValues) { + auxInitValues = initValues; + } + + /** {@inheritDoc} */ + public final void localTarget(int idx, Type type, LocalItem local) { + localTarget = RegisterSpec.makeLocalOptional(idx, type, local); + } + + /** + * Gets the number of primary arguments. + * + * @return {@code >= 0;} the number of primary arguments + */ + protected final int argCount() { + return argCount; + } + + /** + * Gets the width of the arguments (where a category-2 value counts as + * two). + * + * @return {@code >= 0;} the argument width + */ + protected final int argWidth() { + int result = 0; + + for (int i = 0; i < argCount; i++) { + result += args[i].getType().getCategory(); + } + + return result; + } + + /** + * Gets the {@code n}th primary argument. + * + * @param n {@code >= 0, < argCount();} which argument + * @return {@code non-null;} the indicated argument + */ + protected final TypeBearer arg(int n) { + if (n >= argCount) { + throw new IllegalArgumentException("n >= argCount"); + } + + try { + return args[n]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("n < 0"); + } + } + + /** + * Gets the type auxiliary argument. + * + * @return {@code null-ok;} the salient type + */ + protected final Type getAuxType() { + return auxType; + } + + /** + * Gets the {@code int} auxiliary argument. + * + * @return the argument value + */ + protected final int getAuxInt() { + return auxInt; + } + + /** + * Gets the constant auxiliary argument. + * + * @return {@code null-ok;} the argument value + */ + protected final Constant getAuxCst() { + return auxCst; + } + + /** + * Gets the branch target auxiliary argument. + * + * @return the argument value + */ + protected final int getAuxTarget() { + return auxTarget; + } + + /** + * Gets the switch cases auxiliary argument. + * + * @return {@code null-ok;} the argument value + */ + protected final SwitchList getAuxCases() { + return auxCases; + } + + /** + * Gets the init values auxiliary argument. + * + * @return {@code null-ok;} the argument value + */ + protected final ArrayList getInitValues() { + return auxInitValues; + } + /** + * Gets the last local index accessed. + * + * @return {@code >= -1;} the salient local index or {@code -1} if none + * was set since the last time {@link #clearArgs} was called + */ + protected final int getLocalIndex() { + return localIndex; + } + + /** + * Gets whether the loaded local has info in the local variable table. + * + * @return {@code true} if local arg has info in the local variable table + */ + protected final boolean getLocalInfo() { + return localInfo; + } + + /** + * Gets the target local register spec of the current operation, if any. + * The local target spec is the combination of the values indicated + * by a previous call to {@link #localTarget} with the type of what + * should be the sole result set by a call to {@link #setResult} (or + * the combination {@link #clearResult} then {@link #addResult}. + * + * @param isMove {@code true} if the operation being performed on the + * local is a move. This will cause constant values to be propagated + * to the returned local + * @return {@code null-ok;} the salient register spec or {@code null} if no + * local target was set since the last time {@link #clearArgs} was + * called + */ + protected final RegisterSpec getLocalTarget(boolean isMove) { + if (localTarget == null) { + return null; + } + + if (resultCount != 1) { + throw new SimException("local target with " + + ((resultCount == 0) ? "no" : "multiple") + " results"); + } + + TypeBearer result = results[0]; + Type resultType = result.getType(); + Type localType = localTarget.getType(); + + if (resultType == localType) { + /* + * If this is to be a move operation and the result is a + * known value, make the returned localTarget embody that + * value. + */ + if (isMove) { + return localTarget.withType(result); + } else { + return localTarget; + } + } + + if (! Merger.isPossiblyAssignableFrom(localType, resultType)) { + // The result and local types are inconsistent. Complain! + throwLocalMismatch(resultType, localType); + return null; + } + + if (localType == Type.OBJECT) { + /* + * The result type is more specific than the local type, + * so use that instead. + */ + localTarget = localTarget.withType(result); + } + + return localTarget; + } + + /** + * Clears the results. + */ + protected final void clearResult() { + resultCount = 0; + } + + /** + * Sets the results list to be the given single value. + * + *

Note: If there is more than one result value, the + * others may be added by using {@link #addResult}.

+ * + * @param result {@code non-null;} result value + */ + protected final void setResult(TypeBearer result) { + if (result == null) { + throw new NullPointerException("result == null"); + } + + results[0] = result; + resultCount = 1; + } + + /** + * Adds an additional element to the list of results. + * + * @see #setResult + * + * @param result {@code non-null;} result value + */ + protected final void addResult(TypeBearer result) { + if (result == null) { + throw new NullPointerException("result == null"); + } + + results[resultCount] = result; + resultCount++; + } + + /** + * Gets the count of results. This throws an exception if results were + * never set. (Explicitly clearing the results counts as setting them.) + * + * @return {@code >= 0;} the count + */ + protected final int resultCount() { + if (resultCount < 0) { + throw new SimException("results never set"); + } + + return resultCount; + } + + /** + * Gets the width of the results (where a category-2 value counts as + * two). + * + * @return {@code >= 0;} the result width + */ + protected final int resultWidth() { + int width = 0; + + for (int i = 0; i < resultCount; i++) { + width += results[i].getType().getCategory(); + } + + return width; + } + + /** + * Gets the {@code n}th result value. + * + * @param n {@code >= 0, < resultCount();} which result + * @return {@code non-null;} the indicated result value + */ + protected final TypeBearer result(int n) { + if (n >= resultCount) { + throw new IllegalArgumentException("n >= resultCount"); + } + + try { + return results[n]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("n < 0"); + } + } + + /** + * Stores the results of the latest operation into the given frame. If + * there is a local target (see {@link #localTarget}), then the sole + * result is stored to that target; otherwise any results are pushed + * onto the stack. + * + * @param frame {@code non-null;} frame to operate on + */ + protected final void storeResults(Frame frame) { + if (resultCount < 0) { + throw new SimException("results never set"); + } + + if (resultCount == 0) { + // Nothing to do. + return; + } + + if (localTarget != null) { + /* + * Note: getLocalTarget() doesn't necessarily return + * localTarget directly. + */ + frame.getLocals().set(getLocalTarget(false)); + } else { + ExecutionStack stack = frame.getStack(); + for (int i = 0; i < resultCount; i++) { + if (localInfo) { + stack.setLocal(); + } + stack.push(results[i]); + } + } + } + + /** + * Throws an exception that indicates a mismatch in local variable + * types. + * + * @param found {@code non-null;} the encountered type + * @param local {@code non-null;} the local variable's claimed type + */ + public static void throwLocalMismatch(TypeBearer found, + TypeBearer local) { + throw new SimException("local variable type mismatch: " + + "attempt to set or access a value of type " + + found.toHuman() + + " using a local variable of type " + + local.toHuman() + + ". This is symptomatic of .class transformation tools " + + "that ignore local variable information."); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/BasicBlocker.java b/dexlib/src/main/java/com/android/dx/cf/code/BasicBlocker.java new file mode 100644 index 000000000..8fb9560f8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/BasicBlocker.java @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstMemberRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.util.Bits; +import com.android.dx.util.IntList; +import java.util.ArrayList; + +/** + * Utility that identifies basic blocks in bytecode. + */ +public final class BasicBlocker implements BytecodeArray.Visitor { + /** {@code non-null;} method being converted */ + private final ConcreteMethod method; + + /** + * {@code non-null;} work set; bits indicate offsets in need of + * examination + */ + private final int[] workSet; + + /** + * {@code non-null;} live set; bits indicate potentially-live + * opcodes; contrawise, a bit that isn't on is either in the + * middle of an instruction or is a definitely-dead opcode + */ + private final int[] liveSet; + + /** + * {@code non-null;} block start set; bits indicate the starts of + * basic blocks, including the opcodes that start blocks of + * definitely-dead code + */ + private final int[] blockSet; + + /** + * {@code non-null, sparse;} for each instruction offset to a branch of + * some sort, the list of targets for that instruction + */ + private final IntList[] targetLists; + + /** + * {@code non-null, sparse;} for each instruction offset to a throwing + * instruction, the list of exception handlers for that instruction + */ + private final ByteCatchList[] catchLists; + + /** offset of the previously parsed bytecode */ + private int previousOffset; + + /** + * Identifies and enumerates the basic blocks in the given method, + * returning a list of them. The returned list notably omits any + * definitely-dead code that is identified in the process. + * + * @param method {@code non-null;} method to convert + * @return {@code non-null;} list of basic blocks + */ + public static ByteBlockList identifyBlocks(ConcreteMethod method) { + BasicBlocker bb = new BasicBlocker(method); + + bb.doit(); + return bb.getBlockList(); + } + + /** + * Constructs an instance. This class is not publicly instantiable; use + * {@link #identifyBlocks}. + * + * @param method {@code non-null;} method to convert + */ + private BasicBlocker(ConcreteMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + this.method = method; + + /* + * The "+1" below is so the idx-past-end is also valid, + * avoiding a special case, but without preventing + * flow-of-control falling past the end of the method from + * getting properly reported. + */ + int sz = method.getCode().size() + 1; + + workSet = Bits.makeBitSet(sz); + liveSet = Bits.makeBitSet(sz); + blockSet = Bits.makeBitSet(sz); + targetLists = new IntList[sz]; + catchLists = new ByteCatchList[sz]; + previousOffset = -1; + } + + /* + * Note: These methods are defined implementation of the interface + * BytecodeArray.Visitor; since the class isn't publicly + * instantiable, no external code ever gets a chance to actually + * call these methods. + */ + + /** {@inheritDoc} */ + public void visitInvalid(int opcode, int offset, int length) { + visitCommon(offset, length, true); + } + + /** {@inheritDoc} */ + public void visitNoArgs(int opcode, int offset, int length, Type type) { + switch (opcode) { + case ByteOps.IRETURN: + case ByteOps.RETURN: { + visitCommon(offset, length, false); + targetLists[offset] = IntList.EMPTY; + break; + } + case ByteOps.ATHROW: { + visitCommon(offset, length, false); + visitThrowing(offset, length, false); + break; + } + case ByteOps.IALOAD: + case ByteOps.LALOAD: + case ByteOps.FALOAD: + case ByteOps.DALOAD: + case ByteOps.AALOAD: + case ByteOps.BALOAD: + case ByteOps.CALOAD: + case ByteOps.SALOAD: + case ByteOps.IASTORE: + case ByteOps.LASTORE: + case ByteOps.FASTORE: + case ByteOps.DASTORE: + case ByteOps.AASTORE: + case ByteOps.BASTORE: + case ByteOps.CASTORE: + case ByteOps.SASTORE: + case ByteOps.ARRAYLENGTH: + case ByteOps.MONITORENTER: + case ByteOps.MONITOREXIT: { + /* + * These instructions can all throw, so they have to end + * the block they appear in (since throws are branches). + */ + visitCommon(offset, length, true); + visitThrowing(offset, length, true); + break; + } + case ByteOps.IDIV: + case ByteOps.IREM: { + /* + * The int and long versions of division and remainder may + * throw, but not the other types. + */ + visitCommon(offset, length, true); + if ((type == Type.INT) || (type == Type.LONG)) { + visitThrowing(offset, length, true); + } + break; + } + default: { + visitCommon(offset, length, true); + break; + } + } + } + + /** {@inheritDoc} */ + public void visitLocal(int opcode, int offset, int length, + int idx, Type type, int value) { + if (opcode == ByteOps.RET) { + visitCommon(offset, length, false); + targetLists[offset] = IntList.EMPTY; + } else { + visitCommon(offset, length, true); + } + } + + /** {@inheritDoc} */ + public void visitConstant(int opcode, int offset, int length, + Constant cst, int value) { + visitCommon(offset, length, true); + + if ((cst instanceof CstMemberRef) || (cst instanceof CstType) || + (cst instanceof CstString)) { + /* + * Instructions with these sorts of constants have the + * possibility of throwing, so this instruction needs to + * end its block (since it can throw, and possible-throws + * are branch points). + */ + visitThrowing(offset, length, true); + } + } + + /** {@inheritDoc} */ + public void visitBranch(int opcode, int offset, int length, + int target) { + switch (opcode) { + case ByteOps.GOTO: { + visitCommon(offset, length, false); + targetLists[offset] = IntList.makeImmutable(target); + break; + } + case ByteOps.JSR: { + /* + * Each jsr is quarantined into a separate block (containing + * only the jsr instruction) but is otherwise treated + * as a conditional branch. (That is to say, both its + * target and next instruction begin new blocks.) + */ + addWorkIfNecessary(offset, true); + // Fall through to next case... + } + default: { + int next = offset + length; + visitCommon(offset, length, true); + addWorkIfNecessary(next, true); + targetLists[offset] = IntList.makeImmutable(next, target); + break; + } + } + + addWorkIfNecessary(target, true); + } + + /** {@inheritDoc} */ + public void visitSwitch(int opcode, int offset, int length, + SwitchList cases, int padding) { + visitCommon(offset, length, false); + addWorkIfNecessary(cases.getDefaultTarget(), true); + + int sz = cases.size(); + for (int i = 0; i < sz; i++) { + addWorkIfNecessary(cases.getTarget(i), true); + } + + targetLists[offset] = cases.getTargets(); + } + + /** {@inheritDoc} */ + public void visitNewarray(int offset, int length, CstType type, + ArrayList intVals) { + visitCommon(offset, length, true); + visitThrowing(offset, length, true); + } + + /** + * Extracts the list of basic blocks from the bit sets. + * + * @return {@code non-null;} the list of basic blocks + */ + private ByteBlockList getBlockList() { + BytecodeArray bytes = method.getCode(); + ByteBlock[] bbs = new ByteBlock[bytes.size()]; + int count = 0; + + for (int at = 0, next; /*at*/; at = next) { + next = Bits.findFirst(blockSet, at + 1); + if (next < 0) { + break; + } + + if (Bits.get(liveSet, at)) { + /* + * Search backward for the branch or throwing + * instruction at the end of this block, if any. If + * there isn't any, then "next" is the sole target. + */ + IntList targets = null; + int targetsAt = -1; + ByteCatchList blockCatches; + + for (int i = next - 1; i >= at; i--) { + targets = targetLists[i]; + if (targets != null) { + targetsAt = i; + break; + } + } + + if (targets == null) { + targets = IntList.makeImmutable(next); + blockCatches = ByteCatchList.EMPTY; + } else { + blockCatches = catchLists[targetsAt]; + if (blockCatches == null) { + blockCatches = ByteCatchList.EMPTY; + } + } + + bbs[count] = + new ByteBlock(at, at, next, targets, blockCatches); + count++; + } + } + + ByteBlockList result = new ByteBlockList(count); + for (int i = 0; i < count; i++) { + result.set(i, bbs[i]); + } + + return result; + } + + /** + * Does basic block identification. + */ + private void doit() { + BytecodeArray bytes = method.getCode(); + ByteCatchList catches = method.getCatches(); + int catchSz = catches.size(); + + /* + * Start by setting offset 0 as the start of a block and in need + * of work... + */ + Bits.set(workSet, 0); + Bits.set(blockSet, 0); + + /* + * And then process the work set, add new work based on + * exception ranges that are active, and iterate until there's + * nothing left to work on. + */ + while (!Bits.isEmpty(workSet)) { + try { + bytes.processWorkSet(workSet, this); + } catch (IllegalArgumentException ex) { + // Translate the exception. + throw new SimException("flow of control falls off " + + "end of method", + ex); + } + + for (int i = 0; i < catchSz; i++) { + ByteCatchList.Item item = catches.get(i); + int start = item.getStartPc(); + int end = item.getEndPc(); + if (Bits.anyInRange(liveSet, start, end)) { + Bits.set(blockSet, start); + Bits.set(blockSet, end); + addWorkIfNecessary(item.getHandlerPc(), true); + } + } + } + } + + /** + * Sets a bit in the work set, but only if the instruction in question + * isn't yet known to be possibly-live. + * + * @param offset offset to the instruction in question + * @param blockStart {@code true} iff this instruction starts a + * basic block + */ + private void addWorkIfNecessary(int offset, boolean blockStart) { + if (!Bits.get(liveSet, offset)) { + Bits.set(workSet, offset); + } + + if (blockStart) { + Bits.set(blockSet, offset); + } + } + + /** + * Helper method used by all the visitor methods. + * + * @param offset offset to the instruction + * @param length length of the instruction, in bytes + * @param nextIsLive {@code true} iff the instruction after + * the indicated one is possibly-live (because this one isn't an + * unconditional branch, a return, or a switch) + */ + private void visitCommon(int offset, int length, boolean nextIsLive) { + Bits.set(liveSet, offset); + + if (nextIsLive) { + /* + * If the next instruction is flowed to by this one, just + * add it to the work set, and then a subsequent visit*() + * will deal with it as appropriate. + */ + addWorkIfNecessary(offset + length, false); + } else { + /* + * If the next instruction isn't flowed to by this one, + * then mark it as a start of a block but *don't* add it + * to the work set, so that in the final phase we can know + * dead code blocks as those marked as blocks but not also marked + * live. + */ + Bits.set(blockSet, offset + length); + } + } + + /** + * Helper method used by all the visitor methods that deal with + * opcodes that possibly throw. This method should be called after calling + * {@link #visitCommon}. + * + * @param offset offset to the instruction + * @param length length of the instruction, in bytes + * @param nextIsLive {@code true} iff the instruction after + * the indicated one is possibly-live (because this one isn't an + * unconditional throw) + */ + private void visitThrowing(int offset, int length, boolean nextIsLive) { + int next = offset + length; + + if (nextIsLive) { + addWorkIfNecessary(next, true); + } + + ByteCatchList catches = method.getCatches().listFor(offset); + catchLists[offset] = catches; + targetLists[offset] = catches.toTargetList(nextIsLive ? next : -1); + } + + /** + * {@inheritDoc} + */ + public void setPreviousOffset(int offset) { + previousOffset = offset; + } + + /** + * {@inheritDoc} + */ + public int getPreviousOffset() { + return previousOffset; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/ByteBlock.java b/dexlib/src/main/java/com/android/dx/cf/code/ByteBlock.java new file mode 100644 index 000000000..73bbbab42 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/ByteBlock.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.util.Hex; +import com.android.dx.util.IntList; +import com.android.dx.util.LabeledItem; + +/** + * Representation of a basic block in a bytecode array. + */ +public final class ByteBlock implements LabeledItem { + /** {@code >= 0;} label for this block */ + private final int label; + + /** {@code >= 0;} bytecode offset (inclusive) of the start of the block */ + private final int start; + + /** {@code > start;} bytecode offset (exclusive) of the end of the block */ + private final int end; + + /** {@code non-null;} list of successors that this block may branch to */ + private final IntList successors; + + /** {@code non-null;} list of exceptions caught and their handler targets */ + private final ByteCatchList catches; + + /** + * Constructs an instance. + * + * @param label {@code >= 0;} target label for this block + * @param start {@code >= 0;} bytecode offset (inclusive) of the start + * of the block + * @param end {@code > start;} bytecode offset (exclusive) of the end + * of the block + * @param successors {@code non-null;} list of successors that this block may + * branch to + * @param catches {@code non-null;} list of exceptions caught and their + * handler targets + */ + public ByteBlock(int label, int start, int end, IntList successors, + ByteCatchList catches) { + if (label < 0) { + throw new IllegalArgumentException("label < 0"); + } + + if (start < 0) { + throw new IllegalArgumentException("start < 0"); + } + + if (end <= start) { + throw new IllegalArgumentException("end <= start"); + } + + if (successors == null) { + throw new NullPointerException("targets == null"); + } + + int sz = successors.size(); + for (int i = 0; i < sz; i++) { + if (successors.get(i) < 0) { + throw new IllegalArgumentException("successors[" + i + + "] == " + + successors.get(i)); + } + } + + if (catches == null) { + throw new NullPointerException("catches == null"); + } + + this.label = label; + this.start = start; + this.end = end; + this.successors = successors; + this.catches = catches; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return '{' + Hex.u2(label) + ": " + Hex.u2(start) + ".." + + Hex.u2(end) + '}'; + } + + /** + * Gets the label of this block. + * + * @return {@code >= 0;} the label + */ + public int getLabel() { + return label; + } + + /** + * Gets the bytecode offset (inclusive) of the start of this block. + * + * @return {@code >= 0;} the start offset + */ + public int getStart() { + return start; + } + + /** + * Gets the bytecode offset (exclusive) of the end of this block. + * + * @return {@code > getStart();} the end offset + */ + public int getEnd() { + return end; + } + + /** + * Gets the list of successors that this block may branch to + * non-exceptionally. + * + * @return {@code non-null;} the successor list + */ + public IntList getSuccessors() { + return successors; + } + + /** + * Gets the list of exceptions caught and their handler targets. + * + * @return {@code non-null;} the catch list + */ + public ByteCatchList getCatches() { + return catches; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/ByteBlockList.java b/dexlib/src/main/java/com/android/dx/cf/code/ByteBlockList.java new file mode 100644 index 000000000..5370c951f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/ByteBlockList.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.util.Hex; +import com.android.dx.util.LabeledList; + +/** + * List of {@link ByteBlock} instances. + */ +public final class ByteBlockList extends LabeledList { + + /** + * Constructs an instance. + * + * @param size {@code >= 0;} the number of elements to be in the list + */ + public ByteBlockList(int size) { + super(size); + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public ByteBlock get(int n) { + return (ByteBlock) get0(n); + } + + /** + * Gets the block with the given label. + * + * @param label the label to look for + * @return {@code non-null;} the block with the given label + */ + public ByteBlock labelToBlock(int label) { + int idx = indexOfLabel(label); + + if (idx < 0) { + throw new IllegalArgumentException("no such label: " + + Hex.u2(label)); + } + + return get(idx); + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param bb {@code null-ok;} the value to store + */ + public void set(int n, ByteBlock bb) { + super.set(n, bb); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/ByteCatchList.java b/dexlib/src/main/java/com/android/dx/cf/code/ByteCatchList.java new file mode 100644 index 000000000..36c37afe5 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/ByteCatchList.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.FixedSizeList; +import com.android.dx.util.IntList; + +/** + * List of catch entries, that is, the elements of an "exception table," + * which is part of a standard {@code Code} attribute. + */ +public final class ByteCatchList extends FixedSizeList { + /** {@code non-null;} convenient zero-entry instance */ + public static final ByteCatchList EMPTY = new ByteCatchList(0); + + /** + * Constructs an instance. + * + * @param count the number of elements to be in the table + */ + public ByteCatchList(int count) { + super(count); + } + + /** + * Gets the total length of this structure in bytes, when included in + * a {@code Code} attribute. The returned value includes the + * two bytes for {@code exception_table_length}. + * + * @return {@code >= 2;} the total length, in bytes + */ + public int byteLength() { + return 2 + size() * 8; + } + + /** + * Gets the indicated item. + * + * @param n {@code >= 0;} which item + * @return {@code null-ok;} the indicated item + */ + public Item get(int n) { + return (Item) get0(n); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which entry to set + * @param item {@code non-null;} the item + */ + public void set(int n, Item item) { + if (item == null) { + throw new NullPointerException("item == null"); + } + + set0(n, item); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which entry to set + * @param startPc {@code >= 0;} the start pc (inclusive) of the handler's range + * @param endPc {@code >= startPc;} the end pc (exclusive) of the + * handler's range + * @param handlerPc {@code >= 0;} the pc of the exception handler + * @param exceptionClass {@code null-ok;} the exception class or + * {@code null} to catch all exceptions with this handler + */ + public void set(int n, int startPc, int endPc, int handlerPc, + CstType exceptionClass) { + set0(n, new Item(startPc, endPc, handlerPc, exceptionClass)); + } + + /** + * Gets the list of items active at the given address. The result is + * automatically made immutable. + * + * @param pc which address + * @return {@code non-null;} list of exception handlers active at + * {@code pc} + */ + public ByteCatchList listFor(int pc) { + int sz = size(); + Item[] resultArr = new Item[sz]; + int resultSz = 0; + + for (int i = 0; i < sz; i++) { + Item one = get(i); + if (one.covers(pc) && typeNotFound(one, resultArr, resultSz)) { + resultArr[resultSz] = one; + resultSz++; + } + } + + if (resultSz == 0) { + return EMPTY; + } + + ByteCatchList result = new ByteCatchList(resultSz); + for (int i = 0; i < resultSz; i++) { + result.set(i, resultArr[i]); + } + + result.setImmutable(); + return result; + } + + /** + * Helper method for {@link #listFor}, which tells whether a match + * is not found for the exception type of the given item in + * the given array. A match is considered to be either an exact type + * match or the class {@code Object} which represents a catch-all. + * + * @param item {@code non-null;} item with the exception type to look for + * @param arr {@code non-null;} array to search in + * @param count {@code non-null;} maximum number of elements in the array to check + * @return {@code true} iff the exception type is not found + */ + private static boolean typeNotFound(Item item, Item[] arr, int count) { + CstType type = item.getExceptionClass(); + + for (int i = 0; i < count; i++) { + CstType one = arr[i].getExceptionClass(); + if ((one == type) || (one == CstType.OBJECT)) { + return false; + } + } + + return true; + } + + /** + * Returns a target list corresponding to this instance. The result + * is a list of all the exception handler addresses, with the given + * {@code noException} address appended if appropriate. The + * result is automatically made immutable. + * + * @param noException {@code >= -1;} the no-exception address to append, or + * {@code -1} not to append anything + * @return {@code non-null;} list of exception targets, with + * {@code noException} appended if necessary + */ + public IntList toTargetList(int noException) { + if (noException < -1) { + throw new IllegalArgumentException("noException < -1"); + } + + boolean hasDefault = (noException >= 0); + int sz = size(); + + if (sz == 0) { + if (hasDefault) { + /* + * The list is empty, but there is a no-exception + * address; so, the result is just that address. + */ + return IntList.makeImmutable(noException); + } + /* + * The list is empty and there isn't even a no-exception + * address. + */ + return IntList.EMPTY; + } + + IntList result = new IntList(sz + (hasDefault ? 1 : 0)); + + for (int i = 0; i < sz; i++) { + result.add(get(i).getHandlerPc()); + } + + if (hasDefault) { + result.add(noException); + } + + result.setImmutable(); + return result; + } + + /** + * Returns a rop-style catches list equivalent to this one. + * + * @return {@code non-null;} the converted instance + */ + public TypeList toRopCatchList() { + int sz = size(); + if (sz == 0) { + return StdTypeList.EMPTY; + } + + StdTypeList result = new StdTypeList(sz); + + for (int i = 0; i < sz; i++) { + result.set(i, get(i).getExceptionClass().getClassType()); + } + + result.setImmutable(); + return result; + } + + /** + * Item in an exception handler list. + */ + public static class Item { + /** {@code >= 0;} the start pc (inclusive) of the handler's range */ + private final int startPc; + + /** {@code >= startPc;} the end pc (exclusive) of the handler's range */ + private final int endPc; + + /** {@code >= 0;} the pc of the exception handler */ + private final int handlerPc; + + /** {@code null-ok;} the exception class or {@code null} to catch all + * exceptions with this handler */ + private final CstType exceptionClass; + + /** + * Constructs an instance. + * + * @param startPc {@code >= 0;} the start pc (inclusive) of the + * handler's range + * @param endPc {@code >= startPc;} the end pc (exclusive) of the + * handler's range + * @param handlerPc {@code >= 0;} the pc of the exception handler + * @param exceptionClass {@code null-ok;} the exception class or + * {@code null} to catch all exceptions with this handler + */ + public Item(int startPc, int endPc, int handlerPc, + CstType exceptionClass) { + if (startPc < 0) { + throw new IllegalArgumentException("startPc < 0"); + } + + if (endPc < startPc) { + throw new IllegalArgumentException("endPc < startPc"); + } + + if (handlerPc < 0) { + throw new IllegalArgumentException("handlerPc < 0"); + } + + this.startPc = startPc; + this.endPc = endPc; + this.handlerPc = handlerPc; + this.exceptionClass = exceptionClass; + } + + /** + * Gets the start pc (inclusive) of the handler's range. + * + * @return {@code >= 0;} the start pc (inclusive) of the handler's range. + */ + public int getStartPc() { + return startPc; + } + + /** + * Gets the end pc (exclusive) of the handler's range. + * + * @return {@code >= startPc;} the end pc (exclusive) of the + * handler's range. + */ + public int getEndPc() { + return endPc; + } + + /** + * Gets the pc of the exception handler. + * + * @return {@code >= 0;} the pc of the exception handler + */ + public int getHandlerPc() { + return handlerPc; + } + + /** + * Gets the class of exception handled. + * + * @return {@code non-null;} the exception class; {@link CstType#OBJECT} + * if this entry handles all possible exceptions + */ + public CstType getExceptionClass() { + return (exceptionClass != null) ? + exceptionClass : CstType.OBJECT; + } + + /** + * Returns whether the given address is in the range of this item. + * + * @param pc the address + * @return {@code true} iff this item covers {@code pc} + */ + public boolean covers(int pc) { + return (pc >= startPc) && (pc < endPc); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/ByteOps.java b/dexlib/src/main/java/com/android/dx/cf/code/ByteOps.java new file mode 100644 index 000000000..850346a94 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/ByteOps.java @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.util.Hex; + +/** + * Constants and utility methods for dealing with bytecode arrays at an + * opcode level. + */ +public class ByteOps { + // one constant per opcode + public static final int NOP = 0x00; + public static final int ACONST_NULL = 0x01; + public static final int ICONST_M1 = 0x02; + public static final int ICONST_0 = 0x03; + public static final int ICONST_1 = 0x04; + public static final int ICONST_2 = 0x05; + public static final int ICONST_3 = 0x06; + public static final int ICONST_4 = 0x07; + public static final int ICONST_5 = 0x08; + public static final int LCONST_0 = 0x09; + public static final int LCONST_1 = 0x0a; + public static final int FCONST_0 = 0x0b; + public static final int FCONST_1 = 0x0c; + public static final int FCONST_2 = 0x0d; + public static final int DCONST_0 = 0x0e; + public static final int DCONST_1 = 0x0f; + public static final int BIPUSH = 0x10; + public static final int SIPUSH = 0x11; + public static final int LDC = 0x12; + public static final int LDC_W = 0x13; + public static final int LDC2_W = 0x14; + public static final int ILOAD = 0x15; + public static final int LLOAD = 0x16; + public static final int FLOAD = 0x17; + public static final int DLOAD = 0x18; + public static final int ALOAD = 0x19; + public static final int ILOAD_0 = 0x1a; + public static final int ILOAD_1 = 0x1b; + public static final int ILOAD_2 = 0x1c; + public static final int ILOAD_3 = 0x1d; + public static final int LLOAD_0 = 0x1e; + public static final int LLOAD_1 = 0x1f; + public static final int LLOAD_2 = 0x20; + public static final int LLOAD_3 = 0x21; + public static final int FLOAD_0 = 0x22; + public static final int FLOAD_1 = 0x23; + public static final int FLOAD_2 = 0x24; + public static final int FLOAD_3 = 0x25; + public static final int DLOAD_0 = 0x26; + public static final int DLOAD_1 = 0x27; + public static final int DLOAD_2 = 0x28; + public static final int DLOAD_3 = 0x29; + public static final int ALOAD_0 = 0x2a; + public static final int ALOAD_1 = 0x2b; + public static final int ALOAD_2 = 0x2c; + public static final int ALOAD_3 = 0x2d; + public static final int IALOAD = 0x2e; + public static final int LALOAD = 0x2f; + public static final int FALOAD = 0x30; + public static final int DALOAD = 0x31; + public static final int AALOAD = 0x32; + public static final int BALOAD = 0x33; + public static final int CALOAD = 0x34; + public static final int SALOAD = 0x35; + public static final int ISTORE = 0x36; + public static final int LSTORE = 0x37; + public static final int FSTORE = 0x38; + public static final int DSTORE = 0x39; + public static final int ASTORE = 0x3a; + public static final int ISTORE_0 = 0x3b; + public static final int ISTORE_1 = 0x3c; + public static final int ISTORE_2 = 0x3d; + public static final int ISTORE_3 = 0x3e; + public static final int LSTORE_0 = 0x3f; + public static final int LSTORE_1 = 0x40; + public static final int LSTORE_2 = 0x41; + public static final int LSTORE_3 = 0x42; + public static final int FSTORE_0 = 0x43; + public static final int FSTORE_1 = 0x44; + public static final int FSTORE_2 = 0x45; + public static final int FSTORE_3 = 0x46; + public static final int DSTORE_0 = 0x47; + public static final int DSTORE_1 = 0x48; + public static final int DSTORE_2 = 0x49; + public static final int DSTORE_3 = 0x4a; + public static final int ASTORE_0 = 0x4b; + public static final int ASTORE_1 = 0x4c; + public static final int ASTORE_2 = 0x4d; + public static final int ASTORE_3 = 0x4e; + public static final int IASTORE = 0x4f; + public static final int LASTORE = 0x50; + public static final int FASTORE = 0x51; + public static final int DASTORE = 0x52; + public static final int AASTORE = 0x53; + public static final int BASTORE = 0x54; + public static final int CASTORE = 0x55; + public static final int SASTORE = 0x56; + public static final int POP = 0x57; + public static final int POP2 = 0x58; + public static final int DUP = 0x59; + public static final int DUP_X1 = 0x5a; + public static final int DUP_X2 = 0x5b; + public static final int DUP2 = 0x5c; + public static final int DUP2_X1 = 0x5d; + public static final int DUP2_X2 = 0x5e; + public static final int SWAP = 0x5f; + public static final int IADD = 0x60; + public static final int LADD = 0x61; + public static final int FADD = 0x62; + public static final int DADD = 0x63; + public static final int ISUB = 0x64; + public static final int LSUB = 0x65; + public static final int FSUB = 0x66; + public static final int DSUB = 0x67; + public static final int IMUL = 0x68; + public static final int LMUL = 0x69; + public static final int FMUL = 0x6a; + public static final int DMUL = 0x6b; + public static final int IDIV = 0x6c; + public static final int LDIV = 0x6d; + public static final int FDIV = 0x6e; + public static final int DDIV = 0x6f; + public static final int IREM = 0x70; + public static final int LREM = 0x71; + public static final int FREM = 0x72; + public static final int DREM = 0x73; + public static final int INEG = 0x74; + public static final int LNEG = 0x75; + public static final int FNEG = 0x76; + public static final int DNEG = 0x77; + public static final int ISHL = 0x78; + public static final int LSHL = 0x79; + public static final int ISHR = 0x7a; + public static final int LSHR = 0x7b; + public static final int IUSHR = 0x7c; + public static final int LUSHR = 0x7d; + public static final int IAND = 0x7e; + public static final int LAND = 0x7f; + public static final int IOR = 0x80; + public static final int LOR = 0x81; + public static final int IXOR = 0x82; + public static final int LXOR = 0x83; + public static final int IINC = 0x84; + public static final int I2L = 0x85; + public static final int I2F = 0x86; + public static final int I2D = 0x87; + public static final int L2I = 0x88; + public static final int L2F = 0x89; + public static final int L2D = 0x8a; + public static final int F2I = 0x8b; + public static final int F2L = 0x8c; + public static final int F2D = 0x8d; + public static final int D2I = 0x8e; + public static final int D2L = 0x8f; + public static final int D2F = 0x90; + public static final int I2B = 0x91; + public static final int I2C = 0x92; + public static final int I2S = 0x93; + public static final int LCMP = 0x94; + public static final int FCMPL = 0x95; + public static final int FCMPG = 0x96; + public static final int DCMPL = 0x97; + public static final int DCMPG = 0x98; + public static final int IFEQ = 0x99; + public static final int IFNE = 0x9a; + public static final int IFLT = 0x9b; + public static final int IFGE = 0x9c; + public static final int IFGT = 0x9d; + public static final int IFLE = 0x9e; + public static final int IF_ICMPEQ = 0x9f; + public static final int IF_ICMPNE = 0xa0; + public static final int IF_ICMPLT = 0xa1; + public static final int IF_ICMPGE = 0xa2; + public static final int IF_ICMPGT = 0xa3; + public static final int IF_ICMPLE = 0xa4; + public static final int IF_ACMPEQ = 0xa5; + public static final int IF_ACMPNE = 0xa6; + public static final int GOTO = 0xa7; + public static final int JSR = 0xa8; + public static final int RET = 0xa9; + public static final int TABLESWITCH = 0xaa; + public static final int LOOKUPSWITCH = 0xab; + public static final int IRETURN = 0xac; + public static final int LRETURN = 0xad; + public static final int FRETURN = 0xae; + public static final int DRETURN = 0xaf; + public static final int ARETURN = 0xb0; + public static final int RETURN = 0xb1; + public static final int GETSTATIC = 0xb2; + public static final int PUTSTATIC = 0xb3; + public static final int GETFIELD = 0xb4; + public static final int PUTFIELD = 0xb5; + public static final int INVOKEVIRTUAL = 0xb6; + public static final int INVOKESPECIAL = 0xb7; + public static final int INVOKESTATIC = 0xb8; + public static final int INVOKEINTERFACE = 0xb9; + public static final int INVOKEDYNAMIC = 0xba; + public static final int NEW = 0xbb; + public static final int NEWARRAY = 0xbc; + public static final int ANEWARRAY = 0xbd; + public static final int ARRAYLENGTH = 0xbe; + public static final int ATHROW = 0xbf; + public static final int CHECKCAST = 0xc0; + public static final int INSTANCEOF = 0xc1; + public static final int MONITORENTER = 0xc2; + public static final int MONITOREXIT = 0xc3; + public static final int WIDE = 0xc4; + public static final int MULTIANEWARRAY = 0xc5; + public static final int IFNULL = 0xc6; + public static final int IFNONNULL = 0xc7; + public static final int GOTO_W = 0xc8; + public static final int JSR_W = 0xc9; + + // a constant for each valid argument to "newarray" + + public static final int NEWARRAY_BOOLEAN = 4; + public static final int NEWARRAY_CHAR = 5; + public static final int NEWARRAY_FLOAT = 6; + public static final int NEWARRAY_DOUBLE = 7; + public static final int NEWARRAY_BYTE = 8; + public static final int NEWARRAY_SHORT = 9; + public static final int NEWARRAY_INT = 10; + public static final int NEWARRAY_LONG = 11; + + // a constant for each possible instruction format + + /** invalid */ + public static final int FMT_INVALID = 0; + + /** "-": {@code op} */ + public static final int FMT_NO_ARGS = 1; + + /** "0": {@code op}; implies {@code max_locals >= 1} */ + public static final int FMT_NO_ARGS_LOCALS_1 = 2; + + /** "1": {@code op}; implies {@code max_locals >= 2} */ + public static final int FMT_NO_ARGS_LOCALS_2 = 3; + + /** "2": {@code op}; implies {@code max_locals >= 3} */ + public static final int FMT_NO_ARGS_LOCALS_3 = 4; + + /** "3": {@code op}; implies {@code max_locals >= 4} */ + public static final int FMT_NO_ARGS_LOCALS_4 = 5; + + /** "4": {@code op}; implies {@code max_locals >= 5} */ + public static final int FMT_NO_ARGS_LOCALS_5 = 6; + + /** "b": {@code op target target} */ + public static final int FMT_BRANCH = 7; + + /** "c": {@code op target target target target} */ + public static final int FMT_WIDE_BRANCH = 8; + + /** "p": {@code op #cpi #cpi}; constant restricted as specified */ + public static final int FMT_CPI = 9; + + /** + * "l": {@code op local}; category-1 local; implies + * {@code max_locals} is at least two more than the given + * local number + */ + public static final int FMT_LOCAL_1 = 10; + + /** + * "m": {@code op local}; category-2 local; implies + * {@code max_locals} is at least two more than the given + * local number + */ + public static final int FMT_LOCAL_2 = 11; + + /** + * "y": {@code op #byte} ({@code bipush} and + * {@code newarray}) + */ + public static final int FMT_LITERAL_BYTE = 12; + + /** "I": {@code invokeinterface cpi cpi count 0} */ + public static final int FMT_INVOKEINTERFACE = 13; + + /** "L": {@code ldc #cpi}; constant restricted as specified */ + public static final int FMT_LDC = 14; + + /** "S": {@code sipush #byte #byte} */ + public static final int FMT_SIPUSH = 15; + + /** "T": {@code tableswitch ...} */ + public static final int FMT_TABLESWITCH = 16; + + /** "U": {@code lookupswitch ...} */ + public static final int FMT_LOOKUPSWITCH = 17; + + /** "M": {@code multianewarray cpi cpi dims} */ + public static final int FMT_MULTIANEWARRAY = 18; + + /** "W": {@code wide ...} */ + public static final int FMT_WIDE = 19; + + /** mask for the bits representing the opcode format */ + public static final int FMT_MASK = 0x1f; + + /** "I": flag bit for valid cp type for {@code Integer} */ + public static final int CPOK_Integer = 0x20; + + /** "F": flag bit for valid cp type for {@code Float} */ + public static final int CPOK_Float = 0x40; + + /** "J": flag bit for valid cp type for {@code Long} */ + public static final int CPOK_Long = 0x80; + + /** "D": flag bit for valid cp type for {@code Double} */ + public static final int CPOK_Double = 0x100; + + /** "c": flag bit for valid cp type for {@code Class} */ + public static final int CPOK_Class = 0x200; + + /** "s": flag bit for valid cp type for {@code String} */ + public static final int CPOK_String = 0x400; + + /** "f": flag bit for valid cp type for {@code Fieldref} */ + public static final int CPOK_Fieldref = 0x800; + + /** "m": flag bit for valid cp type for {@code Methodref} */ + public static final int CPOK_Methodref = 0x1000; + + /** "i": flag bit for valid cp type for {@code InterfaceMethodref} */ + public static final int CPOK_InterfaceMethodref = 0x2000; + + /** + * {@code non-null;} map from opcodes to format or'ed with allowed constant + * pool types + */ + private static final int[] OPCODE_INFO = new int[256]; + + /** {@code non-null;} map from opcodes to their names */ + private static final String[] OPCODE_NAMES = new String[256]; + + /** {@code non-null;} bigass string describing all the opcodes */ + private static final String OPCODE_DETAILS = + "00 - nop;" + + "01 - aconst_null;" + + "02 - iconst_m1;" + + "03 - iconst_0;" + + "04 - iconst_1;" + + "05 - iconst_2;" + + "06 - iconst_3;" + + "07 - iconst_4;" + + "08 - iconst_5;" + + "09 - lconst_0;" + + "0a - lconst_1;" + + "0b - fconst_0;" + + "0c - fconst_1;" + + "0d - fconst_2;" + + "0e - dconst_0;" + + "0f - dconst_1;" + + "10 y bipush;" + + "11 S sipush;" + + "12 L:IFcs ldc;" + + "13 p:IFcs ldc_w;" + + "14 p:DJ ldc2_w;" + + "15 l iload;" + + "16 m lload;" + + "17 l fload;" + + "18 m dload;" + + "19 l aload;" + + "1a 0 iload_0;" + + "1b 1 iload_1;" + + "1c 2 iload_2;" + + "1d 3 iload_3;" + + "1e 1 lload_0;" + + "1f 2 lload_1;" + + "20 3 lload_2;" + + "21 4 lload_3;" + + "22 0 fload_0;" + + "23 1 fload_1;" + + "24 2 fload_2;" + + "25 3 fload_3;" + + "26 1 dload_0;" + + "27 2 dload_1;" + + "28 3 dload_2;" + + "29 4 dload_3;" + + "2a 0 aload_0;" + + "2b 1 aload_1;" + + "2c 2 aload_2;" + + "2d 3 aload_3;" + + "2e - iaload;" + + "2f - laload;" + + "30 - faload;" + + "31 - daload;" + + "32 - aaload;" + + "33 - baload;" + + "34 - caload;" + + "35 - saload;" + + "36 - istore;" + + "37 - lstore;" + + "38 - fstore;" + + "39 - dstore;" + + "3a - astore;" + + "3b 0 istore_0;" + + "3c 1 istore_1;" + + "3d 2 istore_2;" + + "3e 3 istore_3;" + + "3f 1 lstore_0;" + + "40 2 lstore_1;" + + "41 3 lstore_2;" + + "42 4 lstore_3;" + + "43 0 fstore_0;" + + "44 1 fstore_1;" + + "45 2 fstore_2;" + + "46 3 fstore_3;" + + "47 1 dstore_0;" + + "48 2 dstore_1;" + + "49 3 dstore_2;" + + "4a 4 dstore_3;" + + "4b 0 astore_0;" + + "4c 1 astore_1;" + + "4d 2 astore_2;" + + "4e 3 astore_3;" + + "4f - iastore;" + + "50 - lastore;" + + "51 - fastore;" + + "52 - dastore;" + + "53 - aastore;" + + "54 - bastore;" + + "55 - castore;" + + "56 - sastore;" + + "57 - pop;" + + "58 - pop2;" + + "59 - dup;" + + "5a - dup_x1;" + + "5b - dup_x2;" + + "5c - dup2;" + + "5d - dup2_x1;" + + "5e - dup2_x2;" + + "5f - swap;" + + "60 - iadd;" + + "61 - ladd;" + + "62 - fadd;" + + "63 - dadd;" + + "64 - isub;" + + "65 - lsub;" + + "66 - fsub;" + + "67 - dsub;" + + "68 - imul;" + + "69 - lmul;" + + "6a - fmul;" + + "6b - dmul;" + + "6c - idiv;" + + "6d - ldiv;" + + "6e - fdiv;" + + "6f - ddiv;" + + "70 - irem;" + + "71 - lrem;" + + "72 - frem;" + + "73 - drem;" + + "74 - ineg;" + + "75 - lneg;" + + "76 - fneg;" + + "77 - dneg;" + + "78 - ishl;" + + "79 - lshl;" + + "7a - ishr;" + + "7b - lshr;" + + "7c - iushr;" + + "7d - lushr;" + + "7e - iand;" + + "7f - land;" + + "80 - ior;" + + "81 - lor;" + + "82 - ixor;" + + "83 - lxor;" + + "84 l iinc;" + + "85 - i2l;" + + "86 - i2f;" + + "87 - i2d;" + + "88 - l2i;" + + "89 - l2f;" + + "8a - l2d;" + + "8b - f2i;" + + "8c - f2l;" + + "8d - f2d;" + + "8e - d2i;" + + "8f - d2l;" + + "90 - d2f;" + + "91 - i2b;" + + "92 - i2c;" + + "93 - i2s;" + + "94 - lcmp;" + + "95 - fcmpl;" + + "96 - fcmpg;" + + "97 - dcmpl;" + + "98 - dcmpg;" + + "99 b ifeq;" + + "9a b ifne;" + + "9b b iflt;" + + "9c b ifge;" + + "9d b ifgt;" + + "9e b ifle;" + + "9f b if_icmpeq;" + + "a0 b if_icmpne;" + + "a1 b if_icmplt;" + + "a2 b if_icmpge;" + + "a3 b if_icmpgt;" + + "a4 b if_icmple;" + + "a5 b if_acmpeq;" + + "a6 b if_acmpne;" + + "a7 b goto;" + + "a8 b jsr;" + + "a9 l ret;" + + "aa T tableswitch;" + + "ab U lookupswitch;" + + "ac - ireturn;" + + "ad - lreturn;" + + "ae - freturn;" + + "af - dreturn;" + + "b0 - areturn;" + + "b1 - return;" + + "b2 p:f getstatic;" + + "b3 p:f putstatic;" + + "b4 p:f getfield;" + + "b5 p:f putfield;" + + "b6 p:m invokevirtual;" + + "b7 p:m invokespecial;" + + "b8 p:m invokestatic;" + + "b9 I:i invokeinterface;" + + "bb p:c new;" + + "bc y newarray;" + + "bd p:c anewarray;" + + "be - arraylength;" + + "bf - athrow;" + + "c0 p:c checkcast;" + + "c1 p:c instanceof;" + + "c2 - monitorenter;" + + "c3 - monitorexit;" + + "c4 W wide;" + + "c5 M:c multianewarray;" + + "c6 b ifnull;" + + "c7 b ifnonnull;" + + "c8 c goto_w;" + + "c9 c jsr_w;"; + + static { + // Set up OPCODE_INFO and OPCODE_NAMES. + String s = OPCODE_DETAILS; + int len = s.length(); + + for (int i = 0; i < len; /*i*/) { + int idx = (Character.digit(s.charAt(i), 16) << 4) | + Character.digit(s.charAt(i + 1), 16); + int info; + switch (s.charAt(i + 3)) { + case '-': info = FMT_NO_ARGS; break; + case '0': info = FMT_NO_ARGS_LOCALS_1; break; + case '1': info = FMT_NO_ARGS_LOCALS_2; break; + case '2': info = FMT_NO_ARGS_LOCALS_3; break; + case '3': info = FMT_NO_ARGS_LOCALS_4; break; + case '4': info = FMT_NO_ARGS_LOCALS_5; break; + case 'b': info = FMT_BRANCH; break; + case 'c': info = FMT_WIDE_BRANCH; break; + case 'p': info = FMT_CPI; break; + case 'l': info = FMT_LOCAL_1; break; + case 'm': info = FMT_LOCAL_2; break; + case 'y': info = FMT_LITERAL_BYTE; break; + case 'I': info = FMT_INVOKEINTERFACE; break; + case 'L': info = FMT_LDC; break; + case 'S': info = FMT_SIPUSH; break; + case 'T': info = FMT_TABLESWITCH; break; + case 'U': info = FMT_LOOKUPSWITCH; break; + case 'M': info = FMT_MULTIANEWARRAY; break; + case 'W': info = FMT_WIDE; break; + default: info = FMT_INVALID; break; + } + + i += 5; + if (s.charAt(i - 1) == ':') { + inner: + for (;;) { + switch (s.charAt(i)) { + case 'I': info |= CPOK_Integer; break; + case 'F': info |= CPOK_Float; break; + case 'J': info |= CPOK_Long; break; + case 'D': info |= CPOK_Double; break; + case 'c': info |= CPOK_Class; break; + case 's': info |= CPOK_String; break; + case 'f': info |= CPOK_Fieldref; break; + case 'm': info |= CPOK_Methodref; break; + case 'i': info |= CPOK_InterfaceMethodref; break; + default: break inner; + } + i++; + } + i++; + } + + int endAt = s.indexOf(';', i); + OPCODE_INFO[idx] = info; + OPCODE_NAMES[idx] = s.substring(i, endAt); + i = endAt + 1; + } + } + + /** + * This class is uninstantiable. + */ + private ByteOps() { + // This space intentionally left blank. + } + + /** + * Gets the name of the given opcode. + * + * @param opcode {@code >= 0, <= 255;} the opcode + * @return {@code non-null;} its name + */ + public static String opName(int opcode) { + String result = OPCODE_NAMES[opcode]; + + if (result == null) { + result = "unused_" + Hex.u1(opcode); + OPCODE_NAMES[opcode] = result; + } + + return result; + } + + /** + * Gets the format and allowed cp types of the given opcode. + * + * @param opcode {@code >= 0, <= 255;} the opcode + * @return its format and allowed cp types + */ + public static int opInfo(int opcode) { + return OPCODE_INFO[opcode]; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/BytecodeArray.java b/dexlib/src/main/java/com/android/dx/cf/code/BytecodeArray.java new file mode 100644 index 000000000..424b971a8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/BytecodeArray.java @@ -0,0 +1,1426 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.cf.iface.ParseException; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.ConstantPool; +import com.android.dx.rop.cst.CstDouble; +import com.android.dx.rop.cst.CstFloat; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstKnownNull; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.rop.cst.CstLong; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.util.Bits; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; +import java.util.ArrayList; + +/** + * Bytecode array, which is part of a standard {@code Code} attribute. + */ +public final class BytecodeArray { + /** convenient no-op implementation of {@link Visitor} */ + public static final Visitor EMPTY_VISITOR = new BaseVisitor(); + + /** {@code non-null;} underlying bytes */ + private final ByteArray bytes; + + /** + * {@code non-null;} constant pool to use when resolving constant + * pool indices + */ + private final ConstantPool pool; + + /** + * Constructs an instance. + * + * @param bytes {@code non-null;} underlying bytes + * @param pool {@code non-null;} constant pool to use when + * resolving constant pool indices + */ + public BytecodeArray(ByteArray bytes, ConstantPool pool) { + if (bytes == null) { + throw new NullPointerException("bytes == null"); + } + + if (pool == null) { + throw new NullPointerException("pool == null"); + } + + this.bytes = bytes; + this.pool = pool; + } + + /** + * Gets the underlying byte array. + * + * @return {@code non-null;} the byte array + */ + public ByteArray getBytes() { + return bytes; + } + + /** + * Gets the size of the bytecode array, per se. + * + * @return {@code >= 0;} the length of the bytecode array + */ + public int size() { + return bytes.size(); + } + + /** + * Gets the total length of this structure in bytes, when included in + * a {@code Code} attribute. The returned value includes the + * array size plus four bytes for {@code code_length}. + * + * @return {@code >= 4;} the total length, in bytes + */ + public int byteLength() { + return 4 + bytes.size(); + } + + /** + * Parses each instruction in the array, in order. + * + * @param visitor {@code null-ok;} visitor to call back to for + * each instruction + */ + public void forEach(Visitor visitor) { + int sz = bytes.size(); + int at = 0; + + while (at < sz) { + /* + * Don't record the previous offset here, so that we get to see the + * raw code that initializes the array + */ + at += parseInstruction(at, visitor); + } + } + + /** + * Finds the offset to each instruction in the bytecode array. The + * result is a bit set with the offset of each opcode-per-se flipped on. + * + * @see Bits + * @return {@code non-null;} appropriately constructed bit set + */ + public int[] getInstructionOffsets() { + int sz = bytes.size(); + int[] result = Bits.makeBitSet(sz); + int at = 0; + + while (at < sz) { + Bits.set(result, at, true); + int length = parseInstruction(at, null); + at += length; + } + + return result; + } + + /** + * Processes the given "work set" by repeatedly finding the lowest bit + * in the set, clearing it, and parsing and visiting the instruction at + * the indicated offset (that is, the bit index), repeating until the + * work set is empty. It is expected that the visitor will regularly + * set new bits in the work set during the process. + * + * @param workSet {@code non-null;} the work set to process + * @param visitor {@code non-null;} visitor to call back to for + * each instruction + */ + public void processWorkSet(int[] workSet, Visitor visitor) { + if (visitor == null) { + throw new NullPointerException("visitor == null"); + } + + for (;;) { + int offset = Bits.findFirst(workSet, 0); + if (offset < 0) { + break; + } + Bits.clear(workSet, offset); + parseInstruction(offset, visitor); + visitor.setPreviousOffset(offset); + } + } + + /** + * Parses the instruction at the indicated offset. Indicate the + * result by calling the visitor if supplied and by returning the + * number of bytes consumed by the instruction. + * + *

In order to simplify further processing, the opcodes passed + * to the visitor are canonicalized, altering the opcode to a more + * universal one and making formerly implicit arguments + * explicit. In particular:

+ * + *
    + *
  • The opcodes to push literal constants of primitive types all become + * {@code ldc}. + * E.g., {@code fconst_0}, {@code sipush}, and + * {@code lconst_0} qualify for this treatment.
  • + *
  • {@code aconst_null} becomes {@code ldc} of a + * "known null."
  • + *
  • Shorthand local variable accessors become the corresponding + * longhand. E.g. {@code aload_2} becomes {@code aload}.
  • + *
  • {@code goto_w} and {@code jsr_w} become {@code goto} + * and {@code jsr} (respectively).
  • + *
  • {@code ldc_w} becomes {@code ldc}.
  • + *
  • {@code tableswitch} becomes {@code lookupswitch}. + *
  • Arithmetic, array, and value-returning ops are collapsed + * to the {@code int} variant opcode, with the {@code type} + * argument set to indicate the actual type. E.g., + * {@code fadd} becomes {@code iadd}, but + * {@code type} is passed as {@code Type.FLOAT} in that + * case. Similarly, {@code areturn} becomes + * {@code ireturn}. (However, {@code return} remains + * unchanged.
  • + *
  • Local variable access ops are collapsed to the {@code int} + * variant opcode, with the {@code type} argument set to indicate + * the actual type. E.g., {@code aload} becomes {@code iload}, + * but {@code type} is passed as {@code Type.OBJECT} in + * that case.
  • + *
  • Numeric conversion ops ({@code i2l}, etc.) are left alone + * to avoid too much confustion, but their {@code type} is + * the pushed type. E.g., {@code i2b} gets type + * {@code Type.INT}, and {@code f2d} gets type + * {@code Type.DOUBLE}. Other unaltered opcodes also get + * their pushed type. E.g., {@code arraylength} gets type + * {@code Type.INT}.
  • + *
+ * + * @param offset {@code >= 0, < bytes.size();} offset to the start of the + * instruction + * @param visitor {@code null-ok;} visitor to call back to + * @return the length of the instruction, in bytes + */ + public int parseInstruction(int offset, Visitor visitor) { + if (visitor == null) { + visitor = EMPTY_VISITOR; + } + + try { + int opcode = bytes.getUnsignedByte(offset); + int info = ByteOps.opInfo(opcode); + int fmt = info & ByteOps.FMT_MASK; + + switch (opcode) { + case ByteOps.NOP: { + visitor.visitNoArgs(opcode, offset, 1, Type.VOID); + return 1; + } + case ByteOps.ACONST_NULL: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstKnownNull.THE_ONE, 0); + return 1; + } + case ByteOps.ICONST_M1: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstInteger.VALUE_M1, -1); + return 1; + } + case ByteOps.ICONST_0: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstInteger.VALUE_0, 0); + return 1; + } + case ByteOps.ICONST_1: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstInteger.VALUE_1, 1); + return 1; + } + case ByteOps.ICONST_2: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstInteger.VALUE_2, 2); + return 1; + } + case ByteOps.ICONST_3: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstInteger.VALUE_3, 3); + return 1; + } + case ByteOps.ICONST_4: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstInteger.VALUE_4, 4); + return 1; + } + case ByteOps.ICONST_5: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstInteger.VALUE_5, 5); + return 1; + } + case ByteOps.LCONST_0: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstLong.VALUE_0, 0); + return 1; + } + case ByteOps.LCONST_1: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstLong.VALUE_1, 0); + return 1; + } + case ByteOps.FCONST_0: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstFloat.VALUE_0, 0); + return 1; + } + case ByteOps.FCONST_1: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstFloat.VALUE_1, 0); + return 1; + } + case ByteOps.FCONST_2: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstFloat.VALUE_2, 0); + return 1; + } + case ByteOps.DCONST_0: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstDouble.VALUE_0, 0); + return 1; + } + case ByteOps.DCONST_1: { + visitor.visitConstant(ByteOps.LDC, offset, 1, + CstDouble.VALUE_1, 0); + return 1; + } + case ByteOps.BIPUSH: { + int value = bytes.getByte(offset + 1); + visitor.visitConstant(ByteOps.LDC, offset, 2, + CstInteger.make(value), value); + return 2; + } + case ByteOps.SIPUSH: { + int value = bytes.getShort(offset + 1); + visitor.visitConstant(ByteOps.LDC, offset, 3, + CstInteger.make(value), value); + return 3; + } + case ByteOps.LDC: { + int idx = bytes.getUnsignedByte(offset + 1); + Constant cst = pool.get(idx); + int value = (cst instanceof CstInteger) ? + ((CstInteger) cst).getValue() : 0; + visitor.visitConstant(ByteOps.LDC, offset, 2, cst, value); + return 2; + } + case ByteOps.LDC_W: { + int idx = bytes.getUnsignedShort(offset + 1); + Constant cst = pool.get(idx); + int value = (cst instanceof CstInteger) ? + ((CstInteger) cst).getValue() : 0; + visitor.visitConstant(ByteOps.LDC, offset, 3, cst, value); + return 3; + } + case ByteOps.LDC2_W: { + int idx = bytes.getUnsignedShort(offset + 1); + Constant cst = pool.get(idx); + visitor.visitConstant(ByteOps.LDC2_W, offset, 3, cst, 0); + return 3; + } + case ByteOps.ILOAD: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, + Type.INT, 0); + return 2; + } + case ByteOps.LLOAD: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, + Type.LONG, 0); + return 2; + } + case ByteOps.FLOAD: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, + Type.FLOAT, 0); + return 2; + } + case ByteOps.DLOAD: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, + Type.DOUBLE, 0); + return 2; + } + case ByteOps.ALOAD: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ILOAD, offset, 2, idx, + Type.OBJECT, 0); + return 2; + } + case ByteOps.ILOAD_0: + case ByteOps.ILOAD_1: + case ByteOps.ILOAD_2: + case ByteOps.ILOAD_3: { + int idx = opcode - ByteOps.ILOAD_0; + visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, + Type.INT, 0); + return 1; + } + case ByteOps.LLOAD_0: + case ByteOps.LLOAD_1: + case ByteOps.LLOAD_2: + case ByteOps.LLOAD_3: { + int idx = opcode - ByteOps.LLOAD_0; + visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, + Type.LONG, 0); + return 1; + } + case ByteOps.FLOAD_0: + case ByteOps.FLOAD_1: + case ByteOps.FLOAD_2: + case ByteOps.FLOAD_3: { + int idx = opcode - ByteOps.FLOAD_0; + visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, + Type.FLOAT, 0); + return 1; + } + case ByteOps.DLOAD_0: + case ByteOps.DLOAD_1: + case ByteOps.DLOAD_2: + case ByteOps.DLOAD_3: { + int idx = opcode - ByteOps.DLOAD_0; + visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, + Type.DOUBLE, 0); + return 1; + } + case ByteOps.ALOAD_0: + case ByteOps.ALOAD_1: + case ByteOps.ALOAD_2: + case ByteOps.ALOAD_3: { + int idx = opcode - ByteOps.ALOAD_0; + visitor.visitLocal(ByteOps.ILOAD, offset, 1, idx, + Type.OBJECT, 0); + return 1; + } + case ByteOps.IALOAD: { + visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.INT); + return 1; + } + case ByteOps.LALOAD: { + visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.LONG); + return 1; + } + case ByteOps.FALOAD: { + visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, + Type.FLOAT); + return 1; + } + case ByteOps.DALOAD: { + visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, + Type.DOUBLE); + return 1; + } + case ByteOps.AALOAD: { + visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, + Type.OBJECT); + return 1; + } + case ByteOps.BALOAD: { + /* + * Note: This is a load from either a byte[] or a + * boolean[]. + */ + visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.BYTE); + return 1; + } + case ByteOps.CALOAD: { + visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, Type.CHAR); + return 1; + } + case ByteOps.SALOAD: { + visitor.visitNoArgs(ByteOps.IALOAD, offset, 1, + Type.SHORT); + return 1; + } + case ByteOps.ISTORE: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, + Type.INT, 0); + return 2; + } + case ByteOps.LSTORE: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, + Type.LONG, 0); + return 2; + } + case ByteOps.FSTORE: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, + Type.FLOAT, 0); + return 2; + } + case ByteOps.DSTORE: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, + Type.DOUBLE, 0); + return 2; + } + case ByteOps.ASTORE: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(ByteOps.ISTORE, offset, 2, idx, + Type.OBJECT, 0); + return 2; + } + case ByteOps.ISTORE_0: + case ByteOps.ISTORE_1: + case ByteOps.ISTORE_2: + case ByteOps.ISTORE_3: { + int idx = opcode - ByteOps.ISTORE_0; + visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, + Type.INT, 0); + return 1; + } + case ByteOps.LSTORE_0: + case ByteOps.LSTORE_1: + case ByteOps.LSTORE_2: + case ByteOps.LSTORE_3: { + int idx = opcode - ByteOps.LSTORE_0; + visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, + Type.LONG, 0); + return 1; + } + case ByteOps.FSTORE_0: + case ByteOps.FSTORE_1: + case ByteOps.FSTORE_2: + case ByteOps.FSTORE_3: { + int idx = opcode - ByteOps.FSTORE_0; + visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, + Type.FLOAT, 0); + return 1; + } + case ByteOps.DSTORE_0: + case ByteOps.DSTORE_1: + case ByteOps.DSTORE_2: + case ByteOps.DSTORE_3: { + int idx = opcode - ByteOps.DSTORE_0; + visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, + Type.DOUBLE, 0); + return 1; + } + case ByteOps.ASTORE_0: + case ByteOps.ASTORE_1: + case ByteOps.ASTORE_2: + case ByteOps.ASTORE_3: { + int idx = opcode - ByteOps.ASTORE_0; + visitor.visitLocal(ByteOps.ISTORE, offset, 1, idx, + Type.OBJECT, 0); + return 1; + } + case ByteOps.IASTORE: { + visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, Type.INT); + return 1; + } + case ByteOps.LASTORE: { + visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, + Type.LONG); + return 1; + } + case ByteOps.FASTORE: { + visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, + Type.FLOAT); + return 1; + } + case ByteOps.DASTORE: { + visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, + Type.DOUBLE); + return 1; + } + case ByteOps.AASTORE: { + visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, + Type.OBJECT); + return 1; + } + case ByteOps.BASTORE: { + /* + * Note: This is a load from either a byte[] or a + * boolean[]. + */ + visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, + Type.BYTE); + return 1; + } + case ByteOps.CASTORE: { + visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, + Type.CHAR); + return 1; + } + case ByteOps.SASTORE: { + visitor.visitNoArgs(ByteOps.IASTORE, offset, 1, + Type.SHORT); + return 1; + } + case ByteOps.POP: + case ByteOps.POP2: + case ByteOps.DUP: + case ByteOps.DUP_X1: + case ByteOps.DUP_X2: + case ByteOps.DUP2: + case ByteOps.DUP2_X1: + case ByteOps.DUP2_X2: + case ByteOps.SWAP: { + visitor.visitNoArgs(opcode, offset, 1, Type.VOID); + return 1; + } + case ByteOps.IADD: + case ByteOps.ISUB: + case ByteOps.IMUL: + case ByteOps.IDIV: + case ByteOps.IREM: + case ByteOps.INEG: + case ByteOps.ISHL: + case ByteOps.ISHR: + case ByteOps.IUSHR: + case ByteOps.IAND: + case ByteOps.IOR: + case ByteOps.IXOR: { + visitor.visitNoArgs(opcode, offset, 1, Type.INT); + return 1; + } + case ByteOps.LADD: + case ByteOps.LSUB: + case ByteOps.LMUL: + case ByteOps.LDIV: + case ByteOps.LREM: + case ByteOps.LNEG: + case ByteOps.LSHL: + case ByteOps.LSHR: + case ByteOps.LUSHR: + case ByteOps.LAND: + case ByteOps.LOR: + case ByteOps.LXOR: { + /* + * It's "opcode - 1" because, conveniently enough, all + * these long ops are one past the int variants. + */ + visitor.visitNoArgs(opcode - 1, offset, 1, Type.LONG); + return 1; + } + case ByteOps.FADD: + case ByteOps.FSUB: + case ByteOps.FMUL: + case ByteOps.FDIV: + case ByteOps.FREM: + case ByteOps.FNEG: { + /* + * It's "opcode - 2" because, conveniently enough, all + * these float ops are two past the int variants. + */ + visitor.visitNoArgs(opcode - 2, offset, 1, Type.FLOAT); + return 1; + } + case ByteOps.DADD: + case ByteOps.DSUB: + case ByteOps.DMUL: + case ByteOps.DDIV: + case ByteOps.DREM: + case ByteOps.DNEG: { + /* + * It's "opcode - 3" because, conveniently enough, all + * these double ops are three past the int variants. + */ + visitor.visitNoArgs(opcode - 3, offset, 1, Type.DOUBLE); + return 1; + } + case ByteOps.IINC: { + int idx = bytes.getUnsignedByte(offset + 1); + int value = bytes.getByte(offset + 2); + visitor.visitLocal(opcode, offset, 3, idx, + Type.INT, value); + return 3; + } + case ByteOps.I2L: + case ByteOps.F2L: + case ByteOps.D2L: { + visitor.visitNoArgs(opcode, offset, 1, Type.LONG); + return 1; + } + case ByteOps.I2F: + case ByteOps.L2F: + case ByteOps.D2F: { + visitor.visitNoArgs(opcode, offset, 1, Type.FLOAT); + return 1; + } + case ByteOps.I2D: + case ByteOps.L2D: + case ByteOps.F2D: { + visitor.visitNoArgs(opcode, offset, 1, Type.DOUBLE); + return 1; + } + case ByteOps.L2I: + case ByteOps.F2I: + case ByteOps.D2I: + case ByteOps.I2B: + case ByteOps.I2C: + case ByteOps.I2S: + case ByteOps.LCMP: + case ByteOps.FCMPL: + case ByteOps.FCMPG: + case ByteOps.DCMPL: + case ByteOps.DCMPG: + case ByteOps.ARRAYLENGTH: { + visitor.visitNoArgs(opcode, offset, 1, Type.INT); + return 1; + } + case ByteOps.IFEQ: + case ByteOps.IFNE: + case ByteOps.IFLT: + case ByteOps.IFGE: + case ByteOps.IFGT: + case ByteOps.IFLE: + case ByteOps.IF_ICMPEQ: + case ByteOps.IF_ICMPNE: + case ByteOps.IF_ICMPLT: + case ByteOps.IF_ICMPGE: + case ByteOps.IF_ICMPGT: + case ByteOps.IF_ICMPLE: + case ByteOps.IF_ACMPEQ: + case ByteOps.IF_ACMPNE: + case ByteOps.GOTO: + case ByteOps.JSR: + case ByteOps.IFNULL: + case ByteOps.IFNONNULL: { + int target = offset + bytes.getShort(offset + 1); + visitor.visitBranch(opcode, offset, 3, target); + return 3; + } + case ByteOps.RET: { + int idx = bytes.getUnsignedByte(offset + 1); + visitor.visitLocal(opcode, offset, 2, idx, + Type.RETURN_ADDRESS, 0); + return 2; + } + case ByteOps.TABLESWITCH: { + return parseTableswitch(offset, visitor); + } + case ByteOps.LOOKUPSWITCH: { + return parseLookupswitch(offset, visitor); + } + case ByteOps.IRETURN: { + visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, Type.INT); + return 1; + } + case ByteOps.LRETURN: { + visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, + Type.LONG); + return 1; + } + case ByteOps.FRETURN: { + visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, + Type.FLOAT); + return 1; + } + case ByteOps.DRETURN: { + visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, + Type.DOUBLE); + return 1; + } + case ByteOps.ARETURN: { + visitor.visitNoArgs(ByteOps.IRETURN, offset, 1, + Type.OBJECT); + return 1; + } + case ByteOps.RETURN: + case ByteOps.ATHROW: + case ByteOps.MONITORENTER: + case ByteOps.MONITOREXIT: { + visitor.visitNoArgs(opcode, offset, 1, Type.VOID); + return 1; + } + case ByteOps.GETSTATIC: + case ByteOps.PUTSTATIC: + case ByteOps.GETFIELD: + case ByteOps.PUTFIELD: + case ByteOps.INVOKEVIRTUAL: + case ByteOps.INVOKESPECIAL: + case ByteOps.INVOKESTATIC: + case ByteOps.NEW: + case ByteOps.ANEWARRAY: + case ByteOps.CHECKCAST: + case ByteOps.INSTANCEOF: { + int idx = bytes.getUnsignedShort(offset + 1); + Constant cst = pool.get(idx); + visitor.visitConstant(opcode, offset, 3, cst, 0); + return 3; + } + case ByteOps.INVOKEINTERFACE: { + int idx = bytes.getUnsignedShort(offset + 1); + int count = bytes.getUnsignedByte(offset + 3); + int expectZero = bytes.getUnsignedByte(offset + 4); + Constant cst = pool.get(idx); + visitor.visitConstant(opcode, offset, 5, cst, + count | (expectZero << 8)); + return 5; + } + case ByteOps.INVOKEDYNAMIC: { + throw new ParseException("invokedynamic not supported"); + } + case ByteOps.NEWARRAY: { + return parseNewarray(offset, visitor); + } + case ByteOps.WIDE: { + return parseWide(offset, visitor); + } + case ByteOps.MULTIANEWARRAY: { + int idx = bytes.getUnsignedShort(offset + 1); + int dimensions = bytes.getUnsignedByte(offset + 3); + Constant cst = pool.get(idx); + visitor.visitConstant(opcode, offset, 4, cst, dimensions); + return 4; + } + case ByteOps.GOTO_W: + case ByteOps.JSR_W: { + int target = offset + bytes.getInt(offset + 1); + int newop = + (opcode == ByteOps.GOTO_W) ? ByteOps.GOTO : + ByteOps.JSR; + visitor.visitBranch(newop, offset, 5, target); + return 5; + } + default: { + visitor.visitInvalid(opcode, offset, 1); + return 1; + } + } + } catch (SimException ex) { + ex.addContext("...at bytecode offset " + Hex.u4(offset)); + throw ex; + } catch (RuntimeException ex) { + SimException se = new SimException(ex); + se.addContext("...at bytecode offset " + Hex.u4(offset)); + throw se; + } + } + + /** + * Helper to deal with {@code tableswitch}. + * + * @param offset the offset to the {@code tableswitch} opcode itself + * @param visitor {@code non-null;} visitor to use + * @return instruction length, in bytes + */ + private int parseTableswitch(int offset, Visitor visitor) { + int at = (offset + 4) & ~3; // "at" skips the padding. + + // Collect the padding. + int padding = 0; + for (int i = offset + 1; i < at; i++) { + padding = (padding << 8) | bytes.getUnsignedByte(i); + } + + int defaultTarget = offset + bytes.getInt(at); + int low = bytes.getInt(at + 4); + int high = bytes.getInt(at + 8); + int count = high - low + 1; + at += 12; + + if (low > high) { + throw new SimException("low / high inversion"); + } + + SwitchList cases = new SwitchList(count); + for (int i = 0; i < count; i++) { + int target = offset + bytes.getInt(at); + at += 4; + cases.add(low + i, target); + } + cases.setDefaultTarget(defaultTarget); + cases.removeSuperfluousDefaults(); + cases.setImmutable(); + + int length = at - offset; + visitor.visitSwitch(ByteOps.LOOKUPSWITCH, offset, length, cases, + padding); + + return length; + } + + /** + * Helper to deal with {@code lookupswitch}. + * + * @param offset the offset to the {@code lookupswitch} opcode itself + * @param visitor {@code non-null;} visitor to use + * @return instruction length, in bytes + */ + private int parseLookupswitch(int offset, Visitor visitor) { + int at = (offset + 4) & ~3; // "at" skips the padding. + + // Collect the padding. + int padding = 0; + for (int i = offset + 1; i < at; i++) { + padding = (padding << 8) | bytes.getUnsignedByte(i); + } + + int defaultTarget = offset + bytes.getInt(at); + int npairs = bytes.getInt(at + 4); + at += 8; + + SwitchList cases = new SwitchList(npairs); + for (int i = 0; i < npairs; i++) { + int match = bytes.getInt(at); + int target = offset + bytes.getInt(at + 4); + at += 8; + cases.add(match, target); + } + cases.setDefaultTarget(defaultTarget); + cases.removeSuperfluousDefaults(); + cases.setImmutable(); + + int length = at - offset; + visitor.visitSwitch(ByteOps.LOOKUPSWITCH, offset, length, cases, + padding); + + return length; + } + + /** + * Helper to deal with {@code newarray}. + * + * @param offset the offset to the {@code newarray} opcode itself + * @param visitor {@code non-null;} visitor to use + * @return instruction length, in bytes + */ + private int parseNewarray(int offset, Visitor visitor) { + int value = bytes.getUnsignedByte(offset + 1); + CstType type; + switch (value) { + case ByteOps.NEWARRAY_BOOLEAN: { + type = CstType.BOOLEAN_ARRAY; + break; + } + case ByteOps.NEWARRAY_CHAR: { + type = CstType.CHAR_ARRAY; + break; + } + case ByteOps.NEWARRAY_DOUBLE: { + type = CstType.DOUBLE_ARRAY; + break; + } + case ByteOps.NEWARRAY_FLOAT: { + type = CstType.FLOAT_ARRAY; + break; + } + case ByteOps.NEWARRAY_BYTE: { + type = CstType.BYTE_ARRAY; + break; + } + case ByteOps.NEWARRAY_SHORT: { + type = CstType.SHORT_ARRAY; + break; + } + case ByteOps.NEWARRAY_INT: { + type = CstType.INT_ARRAY; + break; + } + case ByteOps.NEWARRAY_LONG: { + type = CstType.LONG_ARRAY; + break; + } + default: { + throw new SimException("bad newarray code " + + Hex.u1(value)); + } + } + + // Revisit the previous bytecode to find out the length of the array + int previousOffset = visitor.getPreviousOffset(); + ConstantParserVisitor constantVisitor = new ConstantParserVisitor(); + int arrayLength = 0; + + /* + * For visitors that don't record the previous offset, -1 will be + * seen here + */ + if (previousOffset >= 0) { + parseInstruction(previousOffset, constantVisitor); + if (constantVisitor.cst instanceof CstInteger && + constantVisitor.length + previousOffset == offset) { + arrayLength = constantVisitor.value; + + } + } + + /* + * Try to match the array initialization idiom. For example, if the + * subsequent code is initializing an int array, we are expecting the + * following pattern repeatedly: + * dup + * push index + * push value + * *astore + * + * where the index value will be incrimented sequentially from 0 up. + */ + int nInit = 0; + int curOffset = offset+2; + int lastOffset = curOffset; + ArrayList initVals = new ArrayList(); + + if (arrayLength != 0) { + while (true) { + boolean punt = false; + + // First, check if the next bytecode is dup. + int nextByte = bytes.getUnsignedByte(curOffset++); + if (nextByte != ByteOps.DUP) + break; + + /* + * Next, check if the expected array index is pushed to + * the stack. + */ + parseInstruction(curOffset, constantVisitor); + if (constantVisitor.length == 0 || + !(constantVisitor.cst instanceof CstInteger) || + constantVisitor.value != nInit) + break; + + // Next, fetch the init value and record it. + curOffset += constantVisitor.length; + + /* + * Next, find out what kind of constant is pushed onto + * the stack. + */ + parseInstruction(curOffset, constantVisitor); + if (constantVisitor.length == 0 || + !(constantVisitor.cst instanceof CstLiteralBits)) + break; + + curOffset += constantVisitor.length; + initVals.add(constantVisitor.cst); + + nextByte = bytes.getUnsignedByte(curOffset++); + // Now, check if the value is stored to the array properly. + switch (value) { + case ByteOps.NEWARRAY_BYTE: + case ByteOps.NEWARRAY_BOOLEAN: { + if (nextByte != ByteOps.BASTORE) { + punt = true; + } + break; + } + case ByteOps.NEWARRAY_CHAR: { + if (nextByte != ByteOps.CASTORE) { + punt = true; + } + break; + } + case ByteOps.NEWARRAY_DOUBLE: { + if (nextByte != ByteOps.DASTORE) { + punt = true; + } + break; + } + case ByteOps.NEWARRAY_FLOAT: { + if (nextByte != ByteOps.FASTORE) { + punt = true; + } + break; + } + case ByteOps.NEWARRAY_SHORT: { + if (nextByte != ByteOps.SASTORE) { + punt = true; + } + break; + } + case ByteOps.NEWARRAY_INT: { + if (nextByte != ByteOps.IASTORE) { + punt = true; + } + break; + } + case ByteOps.NEWARRAY_LONG: { + if (nextByte != ByteOps.LASTORE) { + punt = true; + } + break; + } + default: + punt = true; + break; + } + if (punt) { + break; + } + lastOffset = curOffset; + nInit++; + } + } + + /* + * For singleton arrays it is still more economical to + * generate the aput. + */ + if (nInit < 2 || nInit != arrayLength) { + visitor.visitNewarray(offset, 2, type, null); + return 2; + } else { + visitor.visitNewarray(offset, lastOffset - offset, type, initVals); + return lastOffset - offset; + } + } + + + /** + * Helper to deal with {@code wide}. + * + * @param offset the offset to the {@code wide} opcode itself + * @param visitor {@code non-null;} visitor to use + * @return instruction length, in bytes + */ + private int parseWide(int offset, Visitor visitor) { + int opcode = bytes.getUnsignedByte(offset + 1); + int idx = bytes.getUnsignedShort(offset + 2); + switch (opcode) { + case ByteOps.ILOAD: { + visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, + Type.INT, 0); + return 4; + } + case ByteOps.LLOAD: { + visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, + Type.LONG, 0); + return 4; + } + case ByteOps.FLOAD: { + visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, + Type.FLOAT, 0); + return 4; + } + case ByteOps.DLOAD: { + visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, + Type.DOUBLE, 0); + return 4; + } + case ByteOps.ALOAD: { + visitor.visitLocal(ByteOps.ILOAD, offset, 4, idx, + Type.OBJECT, 0); + return 4; + } + case ByteOps.ISTORE: { + visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, + Type.INT, 0); + return 4; + } + case ByteOps.LSTORE: { + visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, + Type.LONG, 0); + return 4; + } + case ByteOps.FSTORE: { + visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, + Type.FLOAT, 0); + return 4; + } + case ByteOps.DSTORE: { + visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, + Type.DOUBLE, 0); + return 4; + } + case ByteOps.ASTORE: { + visitor.visitLocal(ByteOps.ISTORE, offset, 4, idx, + Type.OBJECT, 0); + return 4; + } + case ByteOps.RET: { + visitor.visitLocal(opcode, offset, 4, idx, + Type.RETURN_ADDRESS, 0); + return 4; + } + case ByteOps.IINC: { + int value = bytes.getShort(offset + 4); + visitor.visitLocal(opcode, offset, 6, idx, + Type.INT, value); + return 6; + } + default: { + visitor.visitInvalid(ByteOps.WIDE, offset, 1); + return 1; + } + } + } + + /** + * Instruction visitor interface. + */ + public interface Visitor { + /** + * Visits an invalid instruction. + * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length length of the instruction, in bytes + */ + public void visitInvalid(int opcode, int offset, int length); + + /** + * Visits an instruction which has no inline arguments + * (implicit or explicit). + * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length length of the instruction, in bytes + * @param type {@code non-null;} type the instruction operates on + */ + public void visitNoArgs(int opcode, int offset, int length, + Type type); + + /** + * Visits an instruction which has a local variable index argument. + * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length length of the instruction, in bytes + * @param idx the local variable index + * @param type {@code non-null;} the type of the accessed value + * @param value additional literal integer argument, if salient (i.e., + * for {@code iinc}) + */ + public void visitLocal(int opcode, int offset, int length, + int idx, Type type, int value); + + /** + * Visits an instruction which has a (possibly synthetic) + * constant argument, and possibly also an + * additional literal integer argument. In the case of + * {@code multianewarray}, the argument is the count of + * dimensions. In the case of {@code invokeinterface}, + * the argument is the parameter count or'ed with the + * should-be-zero value left-shifted by 8. In the case of entries + * of type {@code int}, the {@code value} field always + * holds the raw value (for convenience of clients). + * + *

Note: In order to avoid giving it a barely-useful + * visitor all its own, {@code newarray} also uses this + * form, passing {@code value} as the array type code and + * {@code cst} as a {@link CstType} instance + * corresponding to the array type.

+ * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length length of the instruction, in bytes + * @param cst {@code non-null;} the constant + * @param value additional literal integer argument, if salient + * (ignore if not) + */ + public void visitConstant(int opcode, int offset, int length, + Constant cst, int value); + + /** + * Visits an instruction which has a branch target argument. + * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length length of the instruction, in bytes + * @param target the absolute (not relative) branch target + */ + public void visitBranch(int opcode, int offset, int length, + int target); + + /** + * Visits a switch instruction. + * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length length of the instruction, in bytes + * @param cases {@code non-null;} list of (value, target) + * pairs, plus the default target + * @param padding the bytes found in the padding area (if any), + * packed + */ + public void visitSwitch(int opcode, int offset, int length, + SwitchList cases, int padding); + + /** + * Visits a newarray instruction. + * + * @param offset offset to the instruction + * @param length length of the instruction, in bytes + * @param type {@code non-null;} the type of the array + * @param initVals {@code non-null;} list of bytecode offsets + * for init values + */ + public void visitNewarray(int offset, int length, CstType type, + ArrayList initVals); + + /** + * Set previous bytecode offset + * @param offset offset of the previous fully parsed bytecode + */ + public void setPreviousOffset(int offset); + + /** + * Get previous bytecode offset + * @return return the recored offset of the previous bytecode + */ + public int getPreviousOffset(); + } + + /** + * Base implementation of {@link Visitor}, which has empty method + * bodies for all methods. + */ + public static class BaseVisitor implements Visitor { + + /** offset of the previously parsed bytecode */ + private int previousOffset; + + BaseVisitor() { + previousOffset = -1; + } + + /** {@inheritDoc} */ + public void visitInvalid(int opcode, int offset, int length) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitNoArgs(int opcode, int offset, int length, + Type type) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitLocal(int opcode, int offset, int length, + int idx, Type type, int value) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitConstant(int opcode, int offset, int length, + Constant cst, int value) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitBranch(int opcode, int offset, int length, + int target) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitSwitch(int opcode, int offset, int length, + SwitchList cases, int padding) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitNewarray(int offset, int length, CstType type, + ArrayList initValues) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void setPreviousOffset(int offset) { + previousOffset = offset; + } + + /** {@inheritDoc} */ + public int getPreviousOffset() { + return previousOffset; + } + } + + /** + * Implementation of {@link Visitor}, which just pays attention + * to constant values. + */ + class ConstantParserVisitor extends BaseVisitor { + Constant cst; + int length; + int value; + + /** Empty constructor */ + ConstantParserVisitor() { + } + + private void clear() { + length = 0; + } + + /** {@inheritDoc} */ + @Override + public void visitInvalid(int opcode, int offset, int length) { + clear(); + } + + /** {@inheritDoc} */ + @Override + public void visitNoArgs(int opcode, int offset, int length, + Type type) { + clear(); + } + + /** {@inheritDoc} */ + @Override + public void visitLocal(int opcode, int offset, int length, + int idx, Type type, int value) { + clear(); + } + + /** {@inheritDoc} */ + @Override + public void visitConstant(int opcode, int offset, int length, + Constant cst, int value) { + this.cst = cst; + this.length = length; + this.value = value; + } + + /** {@inheritDoc} */ + @Override + public void visitBranch(int opcode, int offset, int length, + int target) { + clear(); + } + + /** {@inheritDoc} */ + @Override + public void visitSwitch(int opcode, int offset, int length, + SwitchList cases, int padding) { + clear(); + } + + /** {@inheritDoc} */ + @Override + public void visitNewarray(int offset, int length, CstType type, + ArrayList initVals) { + clear(); + } + + /** {@inheritDoc} */ + @Override + public void setPreviousOffset(int offset) { + // Intentionally left empty + } + + /** {@inheritDoc} */ + @Override + public int getPreviousOffset() { + // Intentionally left empty + return -1; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/ConcreteMethod.java b/dexlib/src/main/java/com/android/dx/cf/code/ConcreteMethod.java new file mode 100644 index 000000000..f88d5684f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/ConcreteMethod.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.cf.attrib.AttCode; +import com.android.dx.cf.attrib.AttLineNumberTable; +import com.android.dx.cf.attrib.AttLocalVariableTable; +import com.android.dx.cf.attrib.AttLocalVariableTypeTable; +import com.android.dx.cf.iface.AttributeList; +import com.android.dx.cf.iface.ClassFile; +import com.android.dx.cf.iface.Method; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Prototype; + +/** + * Container for all the giblets that make up a concrete Java bytecode method. + * It implements {@link Method}, so it provides all the original access + * (by delegation), but it also constructs and keeps useful versions of + * stuff extracted from the method's {@code Code} attribute. + */ +public final class ConcreteMethod implements Method { + /** {@code non-null;} method being wrapped */ + private final Method method; + + /** + * {@code null-ok;} the class's {@code SourceFile} attribute value, + * if any + */ + private final CstString sourceFile; + + /** {@code non-null;} the code attribute */ + private final AttCode attCode; + + /** {@code non-null;} line number list */ + private final LineNumberList lineNumbers; + + /** {@code non-null;} local variable list */ + private final LocalVariableList localVariables; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method to be based on + * @param cf {@code non-null;} the class file that contains this method + * @param keepLines whether to keep the line number information + * (if any) + * @param keepLocals whether to keep the local variable + * information (if any) + */ + public ConcreteMethod(Method method, ClassFile cf, boolean keepLines, boolean keepLocals) { + this(method, cf.getSourceFile(), keepLines, keepLocals); + } + + public ConcreteMethod(Method method, CstString sourceFile, + boolean keepLines, boolean keepLocals) { + this.method = method; + this.sourceFile = sourceFile; + + AttributeList attribs = method.getAttributes(); + this.attCode = (AttCode) attribs.findFirst(AttCode.ATTRIBUTE_NAME); + + AttributeList codeAttribs = attCode.getAttributes(); + + /* + * Combine all LineNumberTable attributes into one, with the + * combined result saved into the instance. The following code + * isn't particularly efficient for doing merges, but as far + * as I know, this situation rarely occurs "in the + * wild," so there's not much point in optimizing for it. + */ + LineNumberList lineNumbers = LineNumberList.EMPTY; + if (keepLines) { + for (AttLineNumberTable lnt = (AttLineNumberTable) + codeAttribs.findFirst(AttLineNumberTable.ATTRIBUTE_NAME); + lnt != null; + lnt = (AttLineNumberTable) codeAttribs.findNext(lnt)) { + lineNumbers = LineNumberList.concat(lineNumbers, + lnt.getLineNumbers()); + } + } + this.lineNumbers = lineNumbers; + + LocalVariableList localVariables = LocalVariableList.EMPTY; + if (keepLocals) { + /* + * Do likewise (and with the same caveat) for + * LocalVariableTable and LocalVariableTypeTable attributes. + * This combines both of these kinds of attribute into a + * single LocalVariableList. + */ + for (AttLocalVariableTable lvt = (AttLocalVariableTable) + codeAttribs.findFirst( + AttLocalVariableTable.ATTRIBUTE_NAME); + lvt != null; + lvt = (AttLocalVariableTable) codeAttribs.findNext(lvt)) { + localVariables = + LocalVariableList.concat(localVariables, + lvt.getLocalVariables()); + } + + LocalVariableList typeList = LocalVariableList.EMPTY; + for (AttLocalVariableTypeTable lvtt = (AttLocalVariableTypeTable) + codeAttribs.findFirst( + AttLocalVariableTypeTable.ATTRIBUTE_NAME); + lvtt != null; + lvtt = + (AttLocalVariableTypeTable) codeAttribs.findNext(lvtt)) { + typeList = + LocalVariableList.concat(typeList, + lvtt.getLocalVariables()); + } + + if (typeList.size() != 0) { + localVariables = + LocalVariableList.mergeDescriptorsAndSignatures( + localVariables, typeList); + } + } + this.localVariables = localVariables; + } + + /** {@inheritDoc} */ + public CstNat getNat() { + return method.getNat(); + } + + /** {@inheritDoc} */ + public CstString getName() { + return method.getName(); + } + + /** {@inheritDoc} */ + public CstString getDescriptor() { + return method.getDescriptor(); + } + + /** {@inheritDoc} */ + public int getAccessFlags() { + return method.getAccessFlags(); + } + + /** {@inheritDoc} */ + public AttributeList getAttributes() { + return method.getAttributes(); + } + + /** {@inheritDoc} */ + public CstType getDefiningClass() { + return method.getDefiningClass(); + } + + /** {@inheritDoc} */ + public Prototype getEffectiveDescriptor() { + return method.getEffectiveDescriptor(); + } + + /** + * Gets the maximum stack size. + * + * @return {@code >= 0;} the maximum stack size + */ + public int getMaxStack() { + return attCode.getMaxStack(); + } + + /** + * Gets the number of locals. + * + * @return {@code >= 0;} the number of locals + */ + public int getMaxLocals() { + return attCode.getMaxLocals(); + } + + /** + * Gets the bytecode array. + * + * @return {@code non-null;} the bytecode array + */ + public BytecodeArray getCode() { + return attCode.getCode(); + } + + /** + * Gets the exception table. + * + * @return {@code non-null;} the exception table + */ + public ByteCatchList getCatches() { + return attCode.getCatches(); + } + + /** + * Gets the line number list. + * + * @return {@code non-null;} the line number list + */ + public LineNumberList getLineNumbers() { + return lineNumbers; + } + + /** + * Gets the local variable list. + * + * @return {@code non-null;} the local variable list + */ + public LocalVariableList getLocalVariables() { + return localVariables; + } + + /** + * Returns a {@link SourcePosition} instance corresponding to the + * given bytecode offset. + * + * @param offset {@code >= 0;} the bytecode offset + * @return {@code non-null;} an appropriate instance + */ + public SourcePosition makeSourcePosistion(int offset) { + return new SourcePosition(sourceFile, offset, + lineNumbers.pcToLine(offset)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/ExecutionStack.java b/dexlib/src/main/java/com/android/dx/cf/code/ExecutionStack.java new file mode 100644 index 000000000..696abc80a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/ExecutionStack.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.Hex; +import com.android.dx.util.MutabilityControl; + +/** + * Representation of a Java method execution stack. + * + *

Note: For the most part, the documentation for this class + * ignores the distinction between {@link Type} and {@link + * TypeBearer}.

+ */ +public final class ExecutionStack extends MutabilityControl { + /** {@code non-null;} array of stack contents */ + private final TypeBearer[] stack; + + /** + * {@code non-null;} array specifying whether stack contents have entries + * in the local variable table + */ + private final boolean[] local; + /** + * {@code >= 0;} stack pointer (points one past the end) / current stack + * size + */ + private int stackPtr; + + /** + * Constructs an instance. + * + * @param maxStack {@code >= 0;} the maximum size of the stack for this + * instance + */ + public ExecutionStack(int maxStack) { + super(maxStack != 0); + stack = new TypeBearer[maxStack]; + local = new boolean[maxStack]; + stackPtr = 0; + } + + /** + * Makes and returns a mutable copy of this instance. + * + * @return {@code non-null;} the copy + */ + public ExecutionStack copy() { + ExecutionStack result = new ExecutionStack(stack.length); + + System.arraycopy(stack, 0, result.stack, 0, stack.length); + System.arraycopy(local, 0, result.local, 0, local.length); + result.stackPtr = stackPtr; + + return result; + } + + /** + * Annotates (adds context to) the given exception with information + * about this instance. + * + * @param ex {@code non-null;} the exception to annotate + */ + public void annotate(ExceptionWithContext ex) { + int limit = stackPtr - 1; + + for (int i = 0; i <= limit; i++) { + String idx = (i == limit) ? "top0" : Hex.u2(limit - i); + + ex.addContext("stack[" + idx + "]: " + + stackElementString(stack[i])); + } + } + + /** + * Replaces all the occurrences of the given uninitialized type in + * this stack with its initialized equivalent. + * + * @param type {@code non-null;} type to replace + */ + public void makeInitialized(Type type) { + if (stackPtr == 0) { + // We have to check for this before checking for immutability. + return; + } + + throwIfImmutable(); + + Type initializedType = type.getInitializedType(); + + for (int i = 0; i < stackPtr; i++) { + if (stack[i] == type) { + stack[i] = initializedType; + } + } + } + + /** + * Gets the maximum stack size for this instance. + * + * @return {@code >= 0;} the max stack size + */ + public int getMaxStack() { + return stack.length; + } + + /** + * Gets the current stack size. + * + * @return {@code >= 0, < getMaxStack();} the current stack size + */ + public int size() { + return stackPtr; + } + + /** + * Clears the stack. (That is, this method pops everything off.) + */ + public void clear() { + throwIfImmutable(); + + for (int i = 0; i < stackPtr; i++) { + stack[i] = null; + local[i] = false; + } + + stackPtr = 0; + } + + /** + * Pushes a value of the given type onto the stack. + * + * @param type {@code non-null;} type of the value + * @throws SimException thrown if there is insufficient room on the + * stack for the value + */ + public void push(TypeBearer type) { + throwIfImmutable(); + + int category; + + try { + type = type.getFrameType(); + category = type.getType().getCategory(); + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("type == null"); + } + + if ((stackPtr + category) > stack.length) { + throwSimException("overflow"); + return; + } + + if (category == 2) { + stack[stackPtr] = null; + stackPtr++; + } + + stack[stackPtr] = type; + stackPtr++; + } + + /** + * Flags the next value pushed onto the stack as having local info. + */ + public void setLocal() { + throwIfImmutable(); + + local[stackPtr] = true; + } + + /** + * Peeks at the {@code n}th element down from the top of the stack. + * {@code n == 0} means to peek at the top of the stack. Note that + * this will return {@code null} if the indicated element is the + * deeper half of a category-2 value. + * + * @param n {@code >= 0;} which element to peek at + * @return {@code null-ok;} the type of value stored at that element + * @throws SimException thrown if {@code n >= size()} + */ + public TypeBearer peek(int n) { + if (n < 0) { + throw new IllegalArgumentException("n < 0"); + } + + if (n >= stackPtr) { + return throwSimException("underflow"); + } + + return stack[stackPtr - n - 1]; + } + + /** + * Peeks at the {@code n}th element down from the top of the + * stack, returning whether or not it has local info. + * + * @param n {@code >= 0;} which element to peek at + * @return {@code true} if the value has local info, {@code false} otherwise + * @throws SimException thrown if {@code n >= size()} + */ + public boolean peekLocal(int n) { + if (n < 0) { + throw new IllegalArgumentException("n < 0"); + } + + if (n >= stackPtr) { + throw new SimException("stack: underflow"); + } + + return local[stackPtr - n - 1]; + } + + /** + * Peeks at the {@code n}th element down from the top of the + * stack, returning the type per se, as opposed to the + * type-bearer. This method is just a convenient shorthand + * for {@code peek(n).getType()}. + * + * @see #peek + */ + public Type peekType(int n) { + return peek(n).getType(); + } + + /** + * Pops the top element off of the stack. + * + * @return {@code non-null;} the type formerly on the top of the stack + * @throws SimException thrown if the stack is empty + */ + public TypeBearer pop() { + throwIfImmutable(); + + TypeBearer result = peek(0); + + stack[stackPtr - 1] = null; + local[stackPtr - 1] = false; + stackPtr -= result.getType().getCategory(); + + return result; + } + + /** + * Changes an element already on a stack. This method is useful in limited + * contexts, particularly when merging two instances. As such, it places + * the following restriction on its behavior: You may only replace + * values with other values of the same category. + * + * @param n {@code >= 0;} which element to change, where {@code 0} is + * the top element of the stack + * @param type {@code non-null;} type of the new value + * @throws SimException thrown if {@code n >= size()} or + * the action is otherwise prohibited + */ + public void change(int n, TypeBearer type) { + throwIfImmutable(); + + try { + type = type.getFrameType(); + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("type == null"); + } + + int idx = stackPtr - n - 1; + TypeBearer orig = stack[idx]; + + if ((orig == null) || + (orig.getType().getCategory() != type.getType().getCategory())) { + throwSimException("incompatible substitution: " + + stackElementString(orig) + " -> " + + stackElementString(type)); + } + + stack[idx] = type; + } + + /** + * Merges this stack with another stack. A new instance is returned if + * this merge results in a change. If no change results, this instance is + * returned. See {@link Merger#mergeStack(ExecutionStack,ExecutionStack) + * Merger.mergeStack()} + * + * @param other {@code non-null;} a stack to merge with + * @return {@code non-null;} the result of the merge + */ + public ExecutionStack merge(ExecutionStack other) { + try { + return Merger.mergeStack(this, other); + } catch (SimException ex) { + ex.addContext("underlay stack:"); + this.annotate(ex); + ex.addContext("overlay stack:"); + other.annotate(ex); + throw ex; + } + } + + /** + * Gets the string form for a stack element. This is the same as + * {@code toString()} except that {@code null} is converted + * to {@code ""}. + * + * @param type {@code null-ok;} the stack element + * @return {@code non-null;} the string form + */ + private static String stackElementString(TypeBearer type) { + if (type == null) { + return ""; + } + + return type.toString(); + } + + /** + * Throws a properly-formatted exception. + * + * @param msg {@code non-null;} useful message + * @return never (keeps compiler happy) + */ + private static TypeBearer throwSimException(String msg) { + throw new SimException("stack: " + msg); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/Frame.java b/dexlib/src/main/java/com/android/dx/cf/code/Frame.java new file mode 100644 index 000000000..cb32b92d9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/Frame.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.util.IntList; + +/** + * Representation of a Java method execution frame. A frame consists + * of a set of locals and a value stack, and it can be told to act on + * them to load and store values between them and an "arguments / + * results" area. + */ +public final class Frame { + /** {@code non-null;} the locals */ + private final LocalsArray locals; + + /** {@code non-null;} the stack */ + private final ExecutionStack stack; + + /** {@code null-ok;} stack of labels of subroutines that this block is nested in */ + private final IntList subroutines; + + /** + * Constructs an instance. + * + * @param locals {@code non-null;} the locals array to use + * @param stack {@code non-null;} the execution stack to use + */ + private Frame(LocalsArray locals, ExecutionStack stack) { + this(locals, stack, IntList.EMPTY); + } + + /** + * Constructs an instance. + * + * @param locals {@code non-null;} the locals array to use + * @param stack {@code non-null;} the execution stack to use + * @param subroutines {@code non-null;} list of subroutine start labels for + * subroutines this frame is nested in + */ + private Frame(LocalsArray locals, + ExecutionStack stack, IntList subroutines) { + if (locals == null) { + throw new NullPointerException("locals == null"); + } + + if (stack == null) { + throw new NullPointerException("stack == null"); + } + + subroutines.throwIfMutable(); + + this.locals = locals; + this.stack = stack; + this.subroutines = subroutines; + } + + /** + * Constructs an instance. The locals array initially consists of + * all-uninitialized values (represented as {@code null}s) and + * the stack starts out empty. + * + * @param maxLocals {@code >= 0;} the maximum number of locals this instance + * can refer to + * @param maxStack {@code >= 0;} the maximum size of the stack for this + * instance + */ + public Frame(int maxLocals, int maxStack) { + this(new OneLocalsArray(maxLocals), new ExecutionStack(maxStack)); + } + + /** + * Makes and returns a mutable copy of this instance. The copy + * contains copies of the locals and stack (that is, it doesn't + * share them with the original). + * + * @return {@code non-null;} the copy + */ + public Frame copy() { + return new Frame(locals.copy(), stack.copy(), subroutines); + } + + /** + * Makes this instance immutable. + */ + public void setImmutable() { + locals.setImmutable(); + stack.setImmutable(); + // "subroutines" is always immutable + } + + /** + * Replaces all the occurrences of the given uninitialized type in + * this frame with its initialized equivalent. + * + * @param type {@code non-null;} type to replace + */ + public void makeInitialized(Type type) { + locals.makeInitialized(type); + stack.makeInitialized(type); + } + + /** + * Gets the locals array for this instance. + * + * @return {@code non-null;} the locals array + */ + public LocalsArray getLocals() { + return locals; + } + + /** + * Gets the execution stack for this instance. + * + * @return {@code non-null;} the execution stack + */ + public ExecutionStack getStack() { + return stack; + } + + /** + * Returns the largest subroutine nesting this block may be in. An + * empty list is returned if this block is not in any subroutine. + * Subroutines are identified by the label of their start block. The + * list is ordered such that the deepest nesting (the actual subroutine + * this block is in) is the last label in the list. + * + * @return {@code non-null;} list as noted above + */ + public IntList getSubroutines() { + return subroutines; + } + + /** + * Initialize this frame with the method's parameters. Used for the first + * frame. + * + * @param params Type list of method parameters. + */ + public void initializeWithParameters(StdTypeList params) { + int at = 0; + int sz = params.size(); + + for (int i = 0; i < sz; i++) { + Type one = params.get(i); + locals.set(at, one); + at += one.getCategory(); + } + } + + /** + * Returns a Frame instance representing the frame state that should + * be used when returning from a subroutine. The stack state of all + * subroutine invocations is identical, but the locals state may differ. + * + * @param startLabel {@code >=0;} The label of the returning subroutine's + * start block + * @param subLabel {@code >=0;} A calling label of a subroutine + * @return {@code null-ok;} an appropriatly-constructed instance, or null + * if label is not in the set + */ + public Frame subFrameForLabel(int startLabel, int subLabel) { + LocalsArray subLocals = null; + + if (locals instanceof LocalsArraySet) { + subLocals = ((LocalsArraySet)locals).subArrayForLabel(subLabel); + } + + IntList newSubroutines; + try { + newSubroutines = subroutines.mutableCopy(); + + if (newSubroutines.pop() != startLabel) { + throw new RuntimeException("returning from invalid subroutine"); + } + newSubroutines.setImmutable(); + } catch (IndexOutOfBoundsException ex) { + throw new RuntimeException("returning from invalid subroutine"); + } catch (NullPointerException ex) { + throw new NullPointerException("can't return from non-subroutine"); + } + + return (subLocals == null) ? null + : new Frame(subLocals, stack, newSubroutines); + } + + /** + * Merges two frames. If the merged result is the same as this frame, + * then this instance is returned. + * + * @param other {@code non-null;} another frame + * @return {@code non-null;} the result of merging the two frames + */ + public Frame mergeWith(Frame other) { + LocalsArray resultLocals; + ExecutionStack resultStack; + IntList resultSubroutines; + + resultLocals = getLocals().merge(other.getLocals()); + resultStack = getStack().merge(other.getStack()); + resultSubroutines = mergeSubroutineLists(other.subroutines); + + resultLocals = adjustLocalsForSubroutines( + resultLocals, resultSubroutines); + + if ((resultLocals == getLocals()) + && (resultStack == getStack()) + && subroutines == resultSubroutines) { + return this; + } + + return new Frame(resultLocals, resultStack, resultSubroutines); + } + + /** + * Merges this frame's subroutine lists with another. The result + * is the deepest common nesting (effectively, the common prefix of the + * two lists). + * + * @param otherSubroutines label list of subroutine start blocks, from + * least-nested to most-nested. + * @return {@code non-null;} merged subroutine nest list as described above + */ + private IntList mergeSubroutineLists(IntList otherSubroutines) { + if (subroutines.equals(otherSubroutines)) { + return subroutines; + } + + IntList resultSubroutines = new IntList(); + + int szSubroutines = subroutines.size(); + int szOthers = otherSubroutines.size(); + for (int i = 0; i < szSubroutines && i < szOthers + && (subroutines.get(i) == otherSubroutines.get(i)); i++) { + resultSubroutines.add(i); + } + + resultSubroutines.setImmutable(); + + return resultSubroutines; + } + + /** + * Adjusts a locals array to account for a merged subroutines list. + * If a frame merge results in, effectively, a subroutine return through + * a throw then the current locals will be a LocalsArraySet that will + * need to be trimmed of all OneLocalsArray elements that relevent to + * the subroutine that is returning. + * + * @param locals {@code non-null;} LocalsArray from before a merge + * @param subroutines {@code non-null;} a label list of subroutine start blocks + * representing the subroutine nesting of the block being merged into. + * @return {@code non-null;} locals set appropriate for merge + */ + private static LocalsArray adjustLocalsForSubroutines( + LocalsArray locals, IntList subroutines) { + if (! (locals instanceof LocalsArraySet)) { + // nothing to see here + return locals; + } + + LocalsArraySet laSet = (LocalsArraySet)locals; + + if (subroutines.size() == 0) { + /* + * We've merged from a subroutine context to a non-subroutine + * context, likely via a throw. Our successor will only need + * to consider the primary locals state, not the state of + * all possible subroutine paths. + */ + + return laSet.getPrimary(); + } + + /* + * It's unclear to me if the locals set needs to be trimmed here. + * If it does, then I believe it is all of the calling blocks + * in the subroutine at the end of "subroutines" passed into + * this method that should be removed. + */ + return laSet; + } + + /** + * Merges this frame with the frame of a subroutine caller at + * {@code predLabel}. Only called on the frame at the first + * block of a subroutine. + * + * @param other {@code non-null;} another frame + * @param subLabel label of subroutine start block + * @param predLabel label of calling block + * @return {@code non-null;} the result of merging the two frames + */ + public Frame mergeWithSubroutineCaller(Frame other, int subLabel, + int predLabel) { + LocalsArray resultLocals; + ExecutionStack resultStack; + + resultLocals = getLocals().mergeWithSubroutineCaller( + other.getLocals(), predLabel); + resultStack = getStack().merge(other.getStack()); + + IntList newOtherSubroutines = other.subroutines.mutableCopy(); + newOtherSubroutines.add(subLabel); + newOtherSubroutines.setImmutable(); + + if ((resultLocals == getLocals()) + && (resultStack == getStack()) + && subroutines.equals(newOtherSubroutines)) { + return this; + } + + IntList resultSubroutines; + + if (subroutines.equals(newOtherSubroutines)) { + resultSubroutines = subroutines; + } else { + /* + * The new subroutines list should be the deepest of the two + * lists being merged, but the postfix of the resultant list + * must be equal to the shorter list. + */ + IntList nonResultSubroutines; + + if (subroutines.size() > newOtherSubroutines.size()) { + resultSubroutines = subroutines; + nonResultSubroutines = newOtherSubroutines; + } else { + resultSubroutines = newOtherSubroutines; + nonResultSubroutines = subroutines; + } + + int szResult = resultSubroutines.size(); + int szNonResult = nonResultSubroutines.size(); + + for (int i = szNonResult - 1; i >=0; i-- ) { + if (nonResultSubroutines.get(i) + != resultSubroutines.get( + i + (szResult - szNonResult))) { + throw new + RuntimeException("Incompatible merged subroutines"); + } + } + + } + + return new Frame(resultLocals, resultStack, resultSubroutines); + } + + /** + * Makes a frame for a subroutine start block, given that this is the + * ending frame of one of the subroutine's calling blocks. Subroutine + * calls may be nested and thus may have nested locals state, so we + * start with an initial state as seen by the subroutine, but keep track + * of the individual locals states that will be expected when the individual + * subroutine calls return. + * + * @param subLabel label of subroutine start block + * @param callerLabel {@code >=0;} label of the caller block where this frame + * came from. + * @return a new instance to begin a called subroutine. + */ + public Frame makeNewSubroutineStartFrame(int subLabel, int callerLabel) { + IntList newSubroutines = subroutines.mutableCopy(); + newSubroutines.add(subLabel); + Frame newFrame = new Frame(locals.getPrimary(), stack, + IntList.makeImmutable(subLabel)); + return newFrame.mergeWithSubroutineCaller(this, subLabel, callerLabel); + } + + /** + * Makes a new frame for an exception handler block invoked from this + * frame. + * + * @param exceptionClass exception that the handler block will handle + * @return new frame + */ + public Frame makeExceptionHandlerStartFrame(CstType exceptionClass) { + ExecutionStack newStack = getStack().copy(); + + newStack.clear(); + newStack.push(exceptionClass); + + return new Frame(getLocals(), newStack, subroutines); + } + + /** + * Annotates (adds context to) the given exception with information + * about this frame. + * + * @param ex {@code non-null;} the exception to annotate + */ + public void annotate(ExceptionWithContext ex) { + locals.annotate(ex); + stack.annotate(ex); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/LineNumberList.java b/dexlib/src/main/java/com/android/dx/cf/code/LineNumberList.java new file mode 100644 index 000000000..f54f8b5e5 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/LineNumberList.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.util.FixedSizeList; + +/** + * List of "line number" entries, which are the contents of + * {@code LineNumberTable} attributes. + */ +public final class LineNumberList extends FixedSizeList { + /** {@code non-null;} zero-size instance */ + public static final LineNumberList EMPTY = new LineNumberList(0); + + /** + * Returns an instance which is the concatenation of the two given + * instances. + * + * @param list1 {@code non-null;} first instance + * @param list2 {@code non-null;} second instance + * @return {@code non-null;} combined instance + */ + public static LineNumberList concat(LineNumberList list1, + LineNumberList list2) { + if (list1 == EMPTY) { + // easy case + return list2; + } + + int sz1 = list1.size(); + int sz2 = list2.size(); + LineNumberList result = new LineNumberList(sz1 + sz2); + + for (int i = 0; i < sz1; i++) { + result.set(i, list1.get(i)); + } + + for (int i = 0; i < sz2; i++) { + result.set(sz1 + i, list2.get(i)); + } + + return result; + } + + /** + * Constructs an instance. + * + * @param count the number of elements to be in the list + */ + public LineNumberList(int count) { + super(count); + } + + /** + * Gets the indicated item. + * + * @param n {@code >= 0;} which item + * @return {@code null-ok;} the indicated item + */ + public Item get(int n) { + return (Item) get0(n); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param item {@code non-null;} the item + */ + public void set(int n, Item item) { + if (item == null) { + throw new NullPointerException("item == null"); + } + + set0(n, item); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param startPc {@code >= 0;} start pc of this item + * @param lineNumber {@code >= 0;} corresponding line number + */ + public void set(int n, int startPc, int lineNumber) { + set0(n, new Item(startPc, lineNumber)); + } + + /** + * Gets the line number associated with the given address. + * + * @param pc {@code >= 0;} the address to look up + * @return {@code >= -1;} the associated line number, or {@code -1} if + * none is known + */ + public int pcToLine(int pc) { + /* + * Line number entries don't have to appear in any particular + * order, so we have to do a linear search. TODO: If + * this turns out to be a bottleneck, consider sorting the + * list prior to use. + */ + int sz = size(); + int bestPc = -1; + int bestLine = -1; + + for (int i = 0; i < sz; i++) { + Item one = get(i); + int onePc = one.getStartPc(); + if ((onePc <= pc) && (onePc > bestPc)) { + bestPc = onePc; + bestLine = one.getLineNumber(); + if (bestPc == pc) { + // We can't do better than this + break; + } + } + } + + return bestLine; + } + + /** + * Item in a line number table. + */ + public static class Item { + /** {@code >= 0;} start pc of this item */ + private final int startPc; + + /** {@code >= 0;} corresponding line number */ + private final int lineNumber; + + /** + * Constructs an instance. + * + * @param startPc {@code >= 0;} start pc of this item + * @param lineNumber {@code >= 0;} corresponding line number + */ + public Item(int startPc, int lineNumber) { + if (startPc < 0) { + throw new IllegalArgumentException("startPc < 0"); + } + + if (lineNumber < 0) { + throw new IllegalArgumentException("lineNumber < 0"); + } + + this.startPc = startPc; + this.lineNumber = lineNumber; + } + + /** + * Gets the start pc of this item. + * + * @return the start pc + */ + public int getStartPc() { + return startPc; + } + + /** + * Gets the line number of this item. + * + * @return the line number + */ + public int getLineNumber() { + return lineNumber; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/LocalVariableList.java b/dexlib/src/main/java/com/android/dx/cf/code/LocalVariableList.java new file mode 100644 index 000000000..4a0bae10c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/LocalVariableList.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.type.Type; +import com.android.dx.util.FixedSizeList; + +/** + * List of "local variable" entries, which are the contents of + * {@code LocalVariableTable} and {@code LocalVariableTypeTable} + * attributes, as well as combinations of the two. + */ +public final class LocalVariableList extends FixedSizeList { + /** {@code non-null;} zero-size instance */ + public static final LocalVariableList EMPTY = new LocalVariableList(0); + + /** + * Returns an instance which is the concatenation of the two given + * instances. The result is immutable. + * + * @param list1 {@code non-null;} first instance + * @param list2 {@code non-null;} second instance + * @return {@code non-null;} combined instance + */ + public static LocalVariableList concat(LocalVariableList list1, + LocalVariableList list2) { + if (list1 == EMPTY) { + // easy case + return list2; + } + + int sz1 = list1.size(); + int sz2 = list2.size(); + LocalVariableList result = new LocalVariableList(sz1 + sz2); + + for (int i = 0; i < sz1; i++) { + result.set(i, list1.get(i)); + } + + for (int i = 0; i < sz2; i++) { + result.set(sz1 + i, list2.get(i)); + } + + result.setImmutable(); + return result; + } + + /** + * Returns an instance which is the result of merging the two + * given instances, where one instance should have only type + * descriptors and the other only type signatures. The merged + * result is identical to the one with descriptors, except that + * any element whose {name, index, start, length} matches an + * element in the signature list gets augmented with the + * corresponding signature. The result is immutable. + * + * @param descriptorList {@code non-null;} list with descriptors + * @param signatureList {@code non-null;} list with signatures + * @return {@code non-null;} the merged result + */ + public static LocalVariableList mergeDescriptorsAndSignatures( + LocalVariableList descriptorList, + LocalVariableList signatureList) { + int descriptorSize = descriptorList.size(); + LocalVariableList result = new LocalVariableList(descriptorSize); + + for (int i = 0; i < descriptorSize; i++) { + Item item = descriptorList.get(i); + Item signatureItem = signatureList.itemToLocal(item); + if (signatureItem != null) { + CstString signature = signatureItem.getSignature(); + item = item.withSignature(signature); + } + result.set(i, item); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. + * + * @param count the number of elements to be in the list + */ + public LocalVariableList(int count) { + super(count); + } + + /** + * Gets the indicated item. + * + * @param n {@code >= 0;} which item + * @return {@code null-ok;} the indicated item + */ + public Item get(int n) { + return (Item) get0(n); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param item {@code non-null;} the item + */ + public void set(int n, Item item) { + if (item == null) { + throw new NullPointerException("item == null"); + } + + set0(n, item); + } + + /** + * Sets the item at the given index. + * + *

Note: At least one of {@code descriptor} or + * {@code signature} must be passed as non-null.

+ * + * @param n {@code >= 0, < size();} which element + * @param startPc {@code >= 0;} the start pc of this variable's scope + * @param length {@code >= 0;} the length (in bytecodes) of this variable's + * scope + * @param name {@code non-null;} the variable's name + * @param descriptor {@code null-ok;} the variable's type descriptor + * @param signature {@code null-ok;} the variable's type signature + * @param index {@code >= 0;} the variable's local index + */ + public void set(int n, int startPc, int length, CstString name, + CstString descriptor, CstString signature, int index) { + set0(n, new Item(startPc, length, name, descriptor, signature, index)); + } + + /** + * Gets the local variable information in this instance which matches + * the given {@link com.android.dx.cf.code.LocalVariableList.Item} + * in all respects but the type descriptor and signature, if any. + * + * @param item {@code non-null;} local variable information to match + * @return {@code null-ok;} the corresponding local variable information stored + * in this instance, or {@code null} if there is no matching + * information + */ + public Item itemToLocal(Item item) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + Item one = (Item) get0(i); + + if ((one != null) && one.matchesAllButType(item)) { + return one; + } + } + + return null; + } + + /** + * Gets the local variable information associated with a given address + * and local index, if any. Note: In standard classfiles, a + * variable's start point is listed as the address of the instruction + * just past the one that sets the variable. + * + * @param pc {@code >= 0;} the address to look up + * @param index {@code >= 0;} the local variable index + * @return {@code null-ok;} the associated local variable information, or + * {@code null} if none is known + */ + public Item pcAndIndexToLocal(int pc, int index) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + Item one = (Item) get0(i); + + if ((one != null) && one.matchesPcAndIndex(pc, index)) { + return one; + } + } + + return null; + } + + /** + * Item in a local variable table. + */ + public static class Item { + /** {@code >= 0;} the start pc of this variable's scope */ + private final int startPc; + + /** {@code >= 0;} the length (in bytecodes) of this variable's scope */ + private final int length; + + /** {@code non-null;} the variable's name */ + private final CstString name; + + /** {@code null-ok;} the variable's type descriptor */ + private final CstString descriptor; + + /** {@code null-ok;} the variable's type signature */ + private final CstString signature; + + /** {@code >= 0;} the variable's local index */ + private final int index; + + /** + * Constructs an instance. + * + *

Note: At least one of {@code descriptor} or + * {@code signature} must be passed as non-null.

+ * + * @param startPc {@code >= 0;} the start pc of this variable's scope + * @param length {@code >= 0;} the length (in bytecodes) of this variable's + * scope + * @param name {@code non-null;} the variable's name + * @param descriptor {@code null-ok;} the variable's type descriptor + * @param signature {@code null-ok;} the variable's type signature + * @param index {@code >= 0;} the variable's local index + */ + public Item(int startPc, int length, CstString name, + CstString descriptor, CstString signature, int index) { + if (startPc < 0) { + throw new IllegalArgumentException("startPc < 0"); + } + + if (length < 0) { + throw new IllegalArgumentException("length < 0"); + } + + if (name == null) { + throw new NullPointerException("name == null"); + } + + if ((descriptor == null) && (signature == null)) { + throw new NullPointerException( + "(descriptor == null) && (signature == null)"); + } + + if (index < 0) { + throw new IllegalArgumentException("index < 0"); + } + + this.startPc = startPc; + this.length = length; + this.name = name; + this.descriptor = descriptor; + this.signature = signature; + this.index = index; + } + + /** + * Gets the start pc of this variable's scope. + * + * @return {@code >= 0;} the start pc of this variable's scope + */ + public int getStartPc() { + return startPc; + } + + /** + * Gets the length (in bytecodes) of this variable's scope. + * + * @return {@code >= 0;} the length (in bytecodes) of this variable's scope + */ + public int getLength() { + return length; + } + + /** + * Gets the variable's type descriptor. + * + * @return {@code null-ok;} the variable's type descriptor + */ + public CstString getDescriptor() { + return descriptor; + } + + /** + * Gets the variable's LocalItem, a (name, signature) tuple + * + * @return {@code null-ok;} the variable's type descriptor + */ + public LocalItem getLocalItem() { + return LocalItem.make(name, signature); + } + + /** + * Gets the variable's type signature. Private because if you need this, + * you want getLocalItem() instead. + * + * @return {@code null-ok;} the variable's type signature + */ + private CstString getSignature() { + return signature; + } + + /** + * Gets the variable's local index. + * + * @return {@code >= 0;} the variable's local index + */ + public int getIndex() { + return index; + } + + /** + * Gets the variable's type descriptor. This is a convenient shorthand + * for {@code Type.intern(getDescriptor().getString())}. + * + * @return {@code non-null;} the variable's type + */ + public Type getType() { + return Type.intern(descriptor.getString()); + } + + /** + * Constructs and returns an instance which is identical to this + * one, except that the signature is changed to the given value. + * + * @param newSignature {@code non-null;} the new signature + * @return {@code non-null;} an appropriately-constructed instance + */ + public Item withSignature(CstString newSignature) { + return new Item(startPc, length, name, descriptor, newSignature, + index); + } + + /** + * Gets whether this instance matches (describes) the given + * address and index. + * + * @param pc {@code >= 0;} the address in question + * @param index {@code >= 0;} the local variable index in question + * @return {@code true} iff this instance matches {@code pc} + * and {@code index} + */ + public boolean matchesPcAndIndex(int pc, int index) { + return (index == this.index) && + (pc >= startPc) && + (pc < (startPc + length)); + } + + /** + * Gets whether this instance matches (describes) the given + * other instance exactly in all fields except type descriptor and + * type signature. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code true} iff this instance matches + */ + public boolean matchesAllButType(Item other) { + return (startPc == other.startPc) + && (length == other.length) + && (index == other.index) + && name.equals(other.name); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/LocalsArray.java b/dexlib/src/main/java/com/android/dx/cf/code/LocalsArray.java new file mode 100644 index 000000000..07ca96da6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/LocalsArray.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.MutabilityControl; +import com.android.dx.util.ToHuman; + +/** + * Representation of an array of local variables, with Java semantics. + * + *

Note: For the most part, the documentation for this class + * ignores the distinction between {@link Type} and {@link + * TypeBearer}.

+ */ +public abstract class LocalsArray extends MutabilityControl implements ToHuman { + + /** + * Constructs an instance, explicitly indicating the mutability. + * + * @param mutable {@code true} if this instance is mutable + */ + protected LocalsArray(boolean mutable) { + super(mutable); + } + + /** + * Makes and returns a mutable copy of this instance. + * + * @return {@code non-null;} the copy + */ + public abstract LocalsArray copy(); + + /** + * Annotates (adds context to) the given exception with information + * about this instance. + * + * @param ex {@code non-null;} the exception to annotate + */ + public abstract void annotate(ExceptionWithContext ex); + + /** + * Replaces all the occurrences of the given uninitialized type in + * this array with its initialized equivalent. + * + * @param type {@code non-null;} type to replace + */ + public abstract void makeInitialized(Type type); + + /** + * Gets the maximum number of locals this instance can refer to. + * + * @return the max locals + */ + public abstract int getMaxLocals(); + + /** + * Sets the type stored at the given local index. If the given type + * is category-2, then (a) the index must be at least two less than + * {@link #getMaxLocals} and (b) the next index gets invalidated + * by the operation. In case of either category, if the previous + * local contains a category-2 value, then it too is invalidated by + * this operation. + * + * @param idx {@code >= 0, < getMaxLocals();} which local + * @param type {@code non-null;} new type for the local at {@code idx} + */ + public abstract void set(int idx, TypeBearer type); + + /** + * Sets the type for the local indicated by the given register spec + * to that register spec (which includes type and optional name + * information). This is identical to calling + * {@code set(spec.getReg(), spec)}. + * + * @param spec {@code non-null;} register spec to use as the basis for the update + */ + public abstract void set(RegisterSpec spec); + + /** + * Invalidates the local at the given index. + * + * @param idx {@code >= 0, < getMaxLocals();} which local + */ + public abstract void invalidate(int idx); + + /** + * Gets the type stored at the given local index, or {@code null} + * if the given local is uninitialized / invalid. + * + * @param idx {@code >= 0, < getMaxLocals();} which local + * @return {@code null-ok;} the type of value stored in that local + */ + public abstract TypeBearer getOrNull(int idx); + + /** + * Gets the type stored at the given local index, only succeeding if + * the given local contains a valid type (though it is allowed to + * be an uninitialized instance). + * + * @param idx {@code >= 0, < getMaxLocals();} which local + * @return {@code non-null;} the type of value stored in that local + * @throws SimException thrown if {@code idx} is valid, but + * the contents are invalid + */ + public abstract TypeBearer get(int idx); + + /** + * Gets the type stored at the given local index, which is expected + * to be an initialized category-1 value. + * + * @param idx {@code >= 0, < getMaxLocals();} which local + * @return {@code non-null;} the type of value stored in that local + * @throws SimException thrown if {@code idx} is valid, but + * one of the following holds: (a) the local is invalid; (b) the local + * contains an uninitialized instance; (c) the local contains a + * category-2 value + */ + public abstract TypeBearer getCategory1(int idx); + + /** + * Gets the type stored at the given local index, which is expected + * to be a category-2 value. + * + * @param idx {@code >= 0, < getMaxLocals();} which local + * @return {@code non-null;} the type of value stored in that local + * @throws SimException thrown if {@code idx} is valid, but + * one of the following holds: (a) the local is invalid; (b) the local + * contains a category-1 value + */ + public abstract TypeBearer getCategory2(int idx); + + /** + * Merges this instance with {@code other}. If the merged result is + * the same as this instance, then this is returned (not a copy). + * + * @param other {@code non-null;} another LocalsArray + * @return {@code non-null;} the merge result, a new instance or this + */ + public abstract LocalsArray merge(LocalsArray other); + + /** + * Merges this instance with a {@code LocalsSet} from a subroutine + * caller. To be used when merging in the first block of a subroutine. + * + * @param other {@code other non-null;} another LocalsArray. The final locals + * state of a subroutine caller. + * @param predLabel the label of the subroutine caller block. + * @return {@code non-null;} the merge result, a new instance or this + */ + public abstract LocalsArraySet mergeWithSubroutineCaller + (LocalsArray other, int predLabel); + + /** + * Gets the locals set appropriate for the current execution context. + * That is, if this is a {@code OneLocalsArray} instance, then return + * {@code this}, otherwise return {@code LocalsArraySet}'s + * primary. + * + * @return locals for this execution context. + */ + protected abstract OneLocalsArray getPrimary(); + +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/LocalsArraySet.java b/dexlib/src/main/java/com/android/dx/cf/code/LocalsArraySet.java new file mode 100644 index 000000000..213816445 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/LocalsArraySet.java @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.Hex; +import java.util.ArrayList; + +/** + * Representation of a set of local variable arrays, with Java semantics. + * This peculiar case is to support in-method subroutines, which can + * have different locals sets for each caller. + * + *

Note: For the most part, the documentation for this class + * ignores the distinction between {@link com.android.dx.rop.type.Type} and {@link + * com.android.dx.rop.type.TypeBearer}.

+ */ +public class LocalsArraySet extends LocalsArray { + + /** + * The primary LocalsArray represents the locals as seen from + * the subroutine itself, which is the merged representation of all the + * individual locals states. + */ + private final OneLocalsArray primary; + + /** + * Indexed by label of caller block: the locals specific to each caller's + * invocation of the subroutine. + */ + private final ArrayList secondaries; + + /** + * Constructs an instance. The locals array initially consists of + * all-uninitialized values (represented as {@code null}s). + * + * @param maxLocals {@code >= 0;} the maximum number of locals this instance + * can refer to + */ + public LocalsArraySet(int maxLocals) { + super(maxLocals != 0); + primary = new OneLocalsArray(maxLocals); + secondaries = new ArrayList(); + } + + /** + * Constructs an instance with the specified primary and secondaries set. + * + * @param primary {@code non-null;} primary locals to use + * @param secondaries {@code non-null;} secondaries set, indexed by subroutine + * caller label. + */ + public LocalsArraySet(OneLocalsArray primary, + ArrayList secondaries) { + super(primary.getMaxLocals() > 0); + + this.primary = primary; + this.secondaries = secondaries; + } + + /** + * Constructs an instance which is a copy of another. + * + * @param toCopy {@code non-null;} instance to copy. + */ + private LocalsArraySet(LocalsArraySet toCopy) { + super(toCopy.getMaxLocals() > 0); + + primary = toCopy.primary.copy(); + secondaries = new ArrayList(toCopy.secondaries.size()); + + int sz = toCopy.secondaries.size(); + for (int i = 0; i < sz; i++) { + LocalsArray la = toCopy.secondaries.get(i); + + if (la == null) { + secondaries.add(null); + } else { + secondaries.add(la.copy()); + } + } + } + + + /** {@inheritDoc} */ + @Override + public void setImmutable() { + primary.setImmutable(); + + for (LocalsArray la : secondaries) { + if (la != null) { + la.setImmutable(); + } + } + super.setImmutable(); + } + + /** {@inheritDoc} */ + @Override + public LocalsArray copy() { + return new LocalsArraySet(this); + } + + /** {@inheritDoc} */ + @Override + public void annotate(ExceptionWithContext ex) { + ex.addContext("(locals array set; primary)"); + primary.annotate(ex); + + int sz = secondaries.size(); + for (int label = 0; label < sz; label++) { + LocalsArray la = secondaries.get(label); + + if (la != null) { + ex.addContext("(locals array set: primary for caller " + + Hex.u2(label) + ')'); + + la.getPrimary().annotate(ex); + } + } + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append("(locals array set; primary)\n"); + + sb.append(getPrimary().toHuman()); + sb.append('\n'); + + int sz = secondaries.size(); + for (int label = 0; label < sz; label++) { + LocalsArray la = secondaries.get(label); + + if (la != null) { + sb.append("(locals array set: primary for caller " + + Hex.u2(label) + ")\n"); + + sb.append(la.getPrimary().toHuman()); + sb.append('\n'); + } + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void makeInitialized(Type type) { + int len = primary.getMaxLocals(); + + if (len == 0) { + // We have to check for this before checking for immutability. + return; + } + + throwIfImmutable(); + + primary.makeInitialized(type); + + for (LocalsArray la : secondaries) { + if (la != null) { + la.makeInitialized(type); + } + } + } + + /** {@inheritDoc} */ + @Override + public int getMaxLocals() { + return primary.getMaxLocals(); + } + + /** {@inheritDoc} */ + @Override + public void set(int idx, TypeBearer type) { + throwIfImmutable(); + + primary.set(idx, type); + + for (LocalsArray la : secondaries) { + if (la != null) { + la.set(idx, type); + } + } + } + + /** {@inheritDoc} */ + @Override + public void set(RegisterSpec spec) { + set(spec.getReg(), spec); + } + + /** {@inheritDoc} */ + @Override + public void invalidate(int idx) { + throwIfImmutable(); + + primary.invalidate(idx); + + for (LocalsArray la : secondaries) { + if (la != null) { + la.invalidate(idx); + } + } + } + + /** {@inheritDoc} */ + @Override + public TypeBearer getOrNull(int idx) { + return primary.getOrNull(idx); + } + + /** {@inheritDoc} */ + @Override + public TypeBearer get(int idx) { + return primary.get(idx); + } + + /** {@inheritDoc} */ + @Override + public TypeBearer getCategory1(int idx) { + return primary.getCategory1(idx); + } + + /** {@inheritDoc} */ + @Override + public TypeBearer getCategory2(int idx) { + return primary.getCategory2(idx); + } + + /** + * Merges this set with another {@code LocalsArraySet} instance. + * + * @param other {@code non-null;} to merge + * @return {@code non-null;} this instance if merge was a no-op, or + * new merged instance. + */ + private LocalsArraySet mergeWithSet(LocalsArraySet other) { + OneLocalsArray newPrimary; + ArrayList newSecondaries; + boolean secondariesChanged = false; + + newPrimary = primary.merge(other.getPrimary()); + + int sz1 = secondaries.size(); + int sz2 = other.secondaries.size(); + int sz = Math.max(sz1, sz2); + newSecondaries = new ArrayList(sz); + + for (int i = 0; i < sz; i++) { + LocalsArray la1 = (i < sz1 ? secondaries.get(i) : null); + LocalsArray la2 = (i < sz2 ? other.secondaries.get(i) : null); + LocalsArray resultla = null; + + if (la1 == la2) { + resultla = la1; + } else if (la1 == null) { + resultla = la2; + } else if (la2 == null) { + resultla = la1; + } else { + try { + resultla = la1.merge(la2); + } catch (SimException ex) { + ex.addContext( + "Merging locals set for caller block " + Hex.u2(i)); + } + } + + secondariesChanged = secondariesChanged || (la1 != resultla); + + newSecondaries.add(resultla); + } + + if ((primary == newPrimary) && ! secondariesChanged ) { + return this; + } + + return new LocalsArraySet(newPrimary, newSecondaries); + } + + /** + * Merges this set with a {@code OneLocalsArray} instance. + * + * @param other {@code non-null;} to merge + * @return {@code non-null;} this instance if merge was a no-op, or + * new merged instance. + */ + private LocalsArraySet mergeWithOne(OneLocalsArray other) { + OneLocalsArray newPrimary; + ArrayList newSecondaries; + boolean secondariesChanged = false; + + newPrimary = primary.merge(other.getPrimary()); + newSecondaries = new ArrayList(secondaries.size()); + + int sz = secondaries.size(); + for (int i = 0; i < sz; i++) { + LocalsArray la = secondaries.get(i); + LocalsArray resultla = null; + + if (la != null) { + try { + resultla = la.merge(other); + } catch (SimException ex) { + ex.addContext("Merging one locals against caller block " + + Hex.u2(i)); + } + } + + secondariesChanged = secondariesChanged || (la != resultla); + + newSecondaries.add(resultla); + } + + if ((primary == newPrimary) && ! secondariesChanged ) { + return this; + } + + return new LocalsArraySet(newPrimary, newSecondaries); + } + + /** {@inheritDoc} */ + @Override + public LocalsArraySet merge(LocalsArray other) { + LocalsArraySet result; + + try { + if (other instanceof LocalsArraySet) { + result = mergeWithSet((LocalsArraySet) other); + } else { + result = mergeWithOne((OneLocalsArray) other); + } + } catch (SimException ex) { + ex.addContext("underlay locals:"); + annotate(ex); + ex.addContext("overlay locals:"); + other.annotate(ex); + throw ex; + } + + result.setImmutable(); + return result; + } + + /** + * Gets the {@code LocalsArray} instance for a specified subroutine + * caller label, or null if label has no locals associated with it. + * + * @param label {@code >= 0;} subroutine caller label + * @return {@code null-ok;} locals if available. + */ + private LocalsArray getSecondaryForLabel(int label) { + if (label >= secondaries.size()) { + return null; + } + + return secondaries.get(label); + } + + /** {@inheritDoc} */ + @Override + public LocalsArraySet mergeWithSubroutineCaller + (LocalsArray other, int predLabel) { + + LocalsArray mine = getSecondaryForLabel(predLabel); + LocalsArray newSecondary; + OneLocalsArray newPrimary; + + newPrimary = primary.merge(other.getPrimary()); + + if (mine == other) { + newSecondary = mine; + } else if (mine == null) { + newSecondary = other; + } else { + newSecondary = mine.merge(other); + } + + if ((newSecondary == mine) && (newPrimary == primary)) { + return this; + } else { + /* + * We're going to re-build a primary as a merge of all the + * secondaries. + */ + newPrimary = null; + + int szSecondaries = secondaries.size(); + int sz = Math.max(predLabel + 1, szSecondaries); + ArrayList newSecondaries = new ArrayList(sz); + for (int i = 0; i < sz; i++) { + LocalsArray la = null; + + if (i == predLabel) { + /* + * This LocalsArray always replaces any existing one, + * since this is the result of a refined iteration. + */ + la = newSecondary; + } else if (i < szSecondaries) { + la = secondaries.get(i); + } + + if (la != null) { + if (newPrimary == null) { + newPrimary = la.getPrimary(); + } else { + newPrimary = newPrimary.merge(la.getPrimary()); + } + } + + newSecondaries.add(la); + } + + LocalsArraySet result + = new LocalsArraySet(newPrimary, newSecondaries); + result.setImmutable(); + return result; + } + } + + /** + * Returns a LocalsArray instance representing the locals state that should + * be used when returning to a subroutine caller. + * + * @param subLabel {@code >= 0;} A calling label of a subroutine + * @return {@code null-ok;} an instance for this subroutine, or null if subroutine + * is not in this set. + */ + public LocalsArray subArrayForLabel(int subLabel) { + LocalsArray result = getSecondaryForLabel(subLabel); + return result; + } + + /**{@inheritDoc}*/ + @Override + protected OneLocalsArray getPrimary() { + return primary; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/Machine.java b/dexlib/src/main/java/com/android/dx/cf/code/Machine.java new file mode 100644 index 000000000..a1905059f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/Machine.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.Type; +import java.util.ArrayList; + +/** + * Interface for machines capable of executing bytecode by acting + * upon a {@link Frame}. A machine conceptually contains four arbitrary-value + * argument slots, slots for several literal-value arguments, and slots for + * branch target information. + */ +public interface Machine { + /** + * Gets the effective prototype of the method that this instance is + * being used for. The effective prototype includes an initial + * {@code this} argument for instance methods. + * + * @return {@code non-null;} the method prototype + */ + public Prototype getPrototype(); + + /** + * Clears the regular and auxiliary arguments area. + */ + public void clearArgs(); + + /** + * Pops the given number of values from the stack (of either category), + * and store them in the arguments area, indicating that there are now + * that many arguments. Also, clear the auxiliary arguments. + * + * @param frame {@code non-null;} frame to operate on + * @param count {@code >= 0;} number of values to pop + */ + public void popArgs(Frame frame, int count); + + /** + * Pops values from the stack of the types indicated by the given + * {@code Prototype} (popped in reverse of the argument + * order, so the first prototype argument type is for the deepest + * element of the stack), and store them in the arguments area, + * indicating that there are now that many arguments. Also, clear + * the auxiliary arguments. + * + * @param frame {@code non-null;} frame to operate on + * @param prototype {@code non-null;} prototype indicating arguments to pop + */ + public void popArgs(Frame frame, Prototype prototype); + + /** + * Pops a value from the stack of the indicated type, and store it + * in the arguments area, indicating that there are now that many + * arguments. Also, clear the auxiliary arguments. + * + * @param frame {@code non-null;} frame to operate on + * @param type {@code non-null;} type of the argument + */ + public void popArgs(Frame frame, Type type); + + /** + * Pops values from the stack of the indicated types (popped in + * reverse argument order, so the first indicated type is for the + * deepest element of the stack), and store them in the arguments + * area, indicating that there are now that many arguments. Also, + * clear the auxiliary arguments. + * + * @param frame {@code non-null;} frame to operate on + * @param type1 {@code non-null;} type of the first argument + * @param type2 {@code non-null;} type of the second argument + */ + public void popArgs(Frame frame, Type type1, Type type2); + + /** + * Pops values from the stack of the indicated types (popped in + * reverse argument order, so the first indicated type is for the + * deepest element of the stack), and store them in the arguments + * area, indicating that there are now that many arguments. Also, + * clear the auxiliary arguments. + * + * @param frame {@code non-null;} frame to operate on + * @param type1 {@code non-null;} type of the first argument + * @param type2 {@code non-null;} type of the second argument + * @param type3 {@code non-null;} type of the third argument + */ + public void popArgs(Frame frame, Type type1, Type type2, Type type3); + + /** + * Loads the local variable with the given index as the sole argument in + * the arguments area. Also, clear the auxiliary arguments. + * + * @param frame {@code non-null;} frame to operate on + * @param idx {@code >= 0;} the local variable index + */ + public void localArg(Frame frame, int idx); + + /** + * Used to specify if a loaded local variable has info in the local + * variable table. + * + * @param local {@code true} if local arg has info in local variable table + */ + public void localInfo(boolean local); + + /** + * Indicates that the salient type of this operation is as + * given. This differentiates between, for example, the various + * arithmetic opcodes, which, by the time they hit a + * {@code Machine} are collapsed to the {@code int} + * variant. (See {@link BytecodeArray#parseInstruction} for + * details.) + * + * @param type {@code non-null;} the salient type of the upcoming operation + */ + public void auxType(Type type); + + /** + * Indicates that there is an auxiliary (inline, not stack) + * argument of type {@code int}, with the given value. + * + *

Note: Perhaps unintuitively, the stack manipulation + * ops (e.g., {@code dup} and {@code swap}) use this to + * indicate the result stack pattern with a straightforward hex + * encoding of the push order starting with least-significant + * nibbles getting pushed first). For example, an all-category-1 + * {@code dup2_x1} sets this to {@code 0x12312}, and the + * other form of that op sets this to + * {@code 0x121}.

+ * + *

Also Note: For {@code switch*} instructions, this is + * used to indicate the padding value (which is only useful for + * verification).

+ * + * @param value the argument value + */ + public void auxIntArg(int value); + + /** + * Indicates that there is an auxiliary (inline, not stack) object + * argument, with the value based on the given constant. + * + *

Note: Some opcodes use both {@code int} and + * constant auxiliary arguments.

+ * + * @param cst {@code non-null;} the constant containing / referencing + * the value + */ + public void auxCstArg(Constant cst); + + /** + * Indicates that there is an auxiliary (inline, not stack) argument + * indicating a branch target. + * + * @param target the argument value + */ + public void auxTargetArg(int target); + + /** + * Indicates that there is an auxiliary (inline, not stack) argument + * consisting of a {@code switch*} table. + * + *

Note: This is generally used in conjunction with + * {@link #auxIntArg} (which holds the padding).

+ * + * @param cases {@code non-null;} the list of key-target pairs, plus the default + * target + */ + public void auxSwitchArg(SwitchList cases); + + /** + * Indicates that there is an auxiliary (inline, not stack) argument + * consisting of a list of initial values for a newly created array. + * + * @param initValues {@code non-null;} the list of constant values to initialize + * the array + */ + public void auxInitValues(ArrayList initValues); + + /** + * Indicates that the target of this operation is the given local. + * + * @param idx {@code >= 0;} the local variable index + * @param type {@code non-null;} the type of the local + * @param local {@code null-ok;} the name and signature of the local, if known + */ + public void localTarget(int idx, Type type, LocalItem local); + + /** + * "Runs" the indicated opcode in an appropriate way, using the arguments + * area as appropriate, and modifying the given frame in response. + * + * @param frame {@code non-null;} frame to operate on + * @param offset {@code >= 0;} byte offset in the method to the opcode being + * run + * @param opcode {@code >= 0;} the opcode to run + */ + public void run(Frame frame, int offset, int opcode); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/Merger.java b/dexlib/src/main/java/com/android/dx/cf/code/Merger.java new file mode 100644 index 000000000..51c31c3a3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/Merger.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.Hex; + +/** + * Utility methods to merge various frame information. + */ +public final class Merger { + /** + * This class is uninstantiable. + */ + private Merger() { + // This space intentionally left blank. + } + + /** + * Merges two locals arrays. If the merged result is the same as the first + * argument, then return the first argument (not a copy). + * + * @param locals1 {@code non-null;} a locals array + * @param locals2 {@code non-null;} another locals array + * @return {@code non-null;} the result of merging the two locals arrays + */ + public static OneLocalsArray mergeLocals(OneLocalsArray locals1, + OneLocalsArray locals2) { + if (locals1 == locals2) { + // Easy out. + return locals1; + } + + int sz = locals1.getMaxLocals(); + OneLocalsArray result = null; + + if (locals2.getMaxLocals() != sz) { + throw new SimException("mismatched maxLocals values"); + } + + for (int i = 0; i < sz; i++) { + TypeBearer tb1 = locals1.getOrNull(i); + TypeBearer tb2 = locals2.getOrNull(i); + TypeBearer resultType = mergeType(tb1, tb2); + if (resultType != tb1) { + /* + * We only need to do anything when the result differs + * from what is in the first array, since that's what the + * result gets initialized to. + */ + if (result == null) { + result = locals1.copy(); + } + + if (resultType == null) { + result.invalidate(i); + } else { + result.set(i, resultType); + } + } + } + + if (result == null) { + return locals1; + } + + result.setImmutable(); + return result; + } + + /** + * Merges two stacks. If the merged result is the same as the first + * argument, then return the first argument (not a copy). + * + * @param stack1 {@code non-null;} a stack + * @param stack2 {@code non-null;} another stack + * @return {@code non-null;} the result of merging the two stacks + */ + public static ExecutionStack mergeStack(ExecutionStack stack1, + ExecutionStack stack2) { + if (stack1 == stack2) { + // Easy out. + return stack1; + } + + int sz = stack1.size(); + ExecutionStack result = null; + + if (stack2.size() != sz) { + throw new SimException("mismatched stack depths"); + } + + for (int i = 0; i < sz; i++) { + TypeBearer tb1 = stack1.peek(i); + TypeBearer tb2 = stack2.peek(i); + TypeBearer resultType = mergeType(tb1, tb2); + if (resultType != tb1) { + /* + * We only need to do anything when the result differs + * from what is in the first stack, since that's what the + * result gets initialized to. + */ + if (result == null) { + result = stack1.copy(); + } + + try { + if (resultType == null) { + throw new SimException("incompatible: " + tb1 + ", " + + tb2); + } else { + result.change(i, resultType); + } + } catch (SimException ex) { + ex.addContext("...while merging stack[" + Hex.u2(i) + "]"); + throw ex; + } + } + } + + if (result == null) { + return stack1; + } + + result.setImmutable(); + return result; + } + + /** + * Merges two frame types. + * + * @param ft1 {@code non-null;} a frame type + * @param ft2 {@code non-null;} another frame type + * @return {@code non-null;} the result of merging the two types + */ + public static TypeBearer mergeType(TypeBearer ft1, TypeBearer ft2) { + if ((ft1 == null) || ft1.equals(ft2)) { + return ft1; + } else if (ft2 == null) { + return null; + } else { + Type type1 = ft1.getType(); + Type type2 = ft2.getType(); + + if (type1 == type2) { + return type1; + } else if (type1.isReference() && type2.isReference()) { + if (type1 == Type.KNOWN_NULL) { + /* + * A known-null merges with any other reference type to + * be that reference type. + */ + return type2; + } else if (type2 == Type.KNOWN_NULL) { + /* + * The same as above, but this time it's type2 that's + * the known-null. + */ + return type1; + } else if (type1.isArray() && type2.isArray()) { + TypeBearer componentUnion = + mergeType(type1.getComponentType(), + type2.getComponentType()); + if (componentUnion == null) { + /* + * At least one of the types is a primitive type, + * so the merged result is just Object. + */ + return Type.OBJECT; + } + return ((Type) componentUnion).getArrayType(); + } else { + /* + * All other unequal reference types get merged to be + * Object in this phase. This is fine here, but it + * won't be the right thing to do in the verifier. + */ + return Type.OBJECT; + } + } else if (type1.isIntlike() && type2.isIntlike()) { + /* + * Merging two non-identical int-like types results in + * the type int. + */ + return Type.INT; + } else { + return null; + } + } + } + + /** + * Returns whether the given supertype is possibly assignable from + * the given subtype. This takes into account primitiveness, + * int-likeness, known-nullness, and array dimensions, but does + * not assume anything about class hierarchy other than that the + * type {@code Object} is the supertype of all reference + * types and all arrays are assignable to + * {@code Serializable} and {@code Cloneable}. + * + * @param supertypeBearer {@code non-null;} the supertype + * @param subtypeBearer {@code non-null;} the subtype + */ + public static boolean isPossiblyAssignableFrom(TypeBearer supertypeBearer, + TypeBearer subtypeBearer) { + Type supertype = supertypeBearer.getType(); + Type subtype = subtypeBearer.getType(); + + if (supertype.equals(subtype)) { + // Easy out. + return true; + } + + int superBt = supertype.getBasicType(); + int subBt = subtype.getBasicType(); + + // Treat return types as Object for the purposes of this method. + + if (superBt == Type.BT_ADDR) { + supertype = Type.OBJECT; + superBt = Type.BT_OBJECT; + } + + if (subBt == Type.BT_ADDR) { + subtype = Type.OBJECT; + subBt = Type.BT_OBJECT; + } + + if ((superBt != Type.BT_OBJECT) || (subBt != Type.BT_OBJECT)) { + /* + * No two distinct primitive types are assignable in this sense, + * unless they are both int-like. + */ + return supertype.isIntlike() && subtype.isIntlike(); + } + + // At this point, we know both types are reference types. + + if (supertype == Type.KNOWN_NULL) { + /* + * A known-null supertype is only assignable from another + * known-null (handled in the easy out at the top of the + * method). + */ + return false; + } else if (subtype == Type.KNOWN_NULL) { + /* + * A known-null subtype is in fact assignable to any + * reference type. + */ + return true; + } else if (supertype == Type.OBJECT) { + /* + * Object is assignable from any reference type. + */ + return true; + } else if (supertype.isArray()) { + // The supertype is an array type. + if (! subtype.isArray()) { + // The subtype isn't an array, and so can't be assignable. + return false; + } + + /* + * Strip off as many matched component types from both + * types as possible, and check the assignability of the + * results. + */ + do { + supertype = supertype.getComponentType(); + subtype = subtype.getComponentType(); + } while (supertype.isArray() && subtype.isArray()); + + return isPossiblyAssignableFrom(supertype, subtype); + } else if (subtype.isArray()) { + /* + * Other than Object (handled above), array types are + * assignable only to Serializable and Cloneable. + */ + return (supertype == Type.SERIALIZABLE) || + (supertype == Type.CLONEABLE); + } else { + /* + * All other unequal reference types are considered at + * least possibly assignable. + */ + return true; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/OneLocalsArray.java b/dexlib/src/main/java/com/android/dx/cf/code/OneLocalsArray.java new file mode 100644 index 000000000..f22cf58ba --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/OneLocalsArray.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.Hex; + +/** + * Representation of an array of local variables, with Java semantics. + * + *

Note: For the most part, the documentation for this class + * ignores the distinction between {@link com.android.dx.rop.type.Type} and {@link + * com.android.dx.rop.type.TypeBearer}.

+ */ +public class OneLocalsArray extends LocalsArray { + /** {@code non-null;} actual array */ + private final TypeBearer[] locals; + + /** + * Constructs an instance. The locals array initially consists of + * all-uninitialized values (represented as {@code null}s). + * + * @param maxLocals {@code >= 0;} the maximum number of locals this instance + * can refer to + */ + public OneLocalsArray(int maxLocals) { + super(maxLocals != 0); + locals = new TypeBearer[maxLocals]; + } + + /** {@inheritDoc} */ + public OneLocalsArray copy() { + OneLocalsArray result = new OneLocalsArray(locals.length); + + System.arraycopy(locals, 0, result.locals, 0, locals.length); + + return result; + } + + /** {@inheritDoc} */ + public void annotate(ExceptionWithContext ex) { + for (int i = 0; i < locals.length; i++) { + TypeBearer type = locals[i]; + String s = (type == null) ? "" : type.toString(); + ex.addContext("locals[" + Hex.u2(i) + "]: " + s); + } + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < locals.length; i++) { + TypeBearer type = locals[i]; + String s = (type == null) ? "" : type.toString(); + sb.append("locals[" + Hex.u2(i) + "]: " + s + "\n"); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + public void makeInitialized(Type type) { + int len = locals.length; + + if (len == 0) { + // We have to check for this before checking for immutability. + return; + } + + throwIfImmutable(); + + Type initializedType = type.getInitializedType(); + + for (int i = 0; i < len; i++) { + if (locals[i] == type) { + locals[i] = initializedType; + } + } + } + + /** {@inheritDoc} */ + public int getMaxLocals() { + return locals.length; + } + + /** {@inheritDoc} */ + public void set(int idx, TypeBearer type) { + throwIfImmutable(); + + try { + type = type.getFrameType(); + } catch (NullPointerException ex) { + // Elucidate the exception + throw new NullPointerException("type == null"); + } + + if (idx < 0) { + throw new IndexOutOfBoundsException("idx < 0"); + } + + // Make highest possible out-of-bounds check happen first. + if (type.getType().isCategory2()) { + locals[idx + 1] = null; + } + + locals[idx] = type; + + if (idx != 0) { + TypeBearer prev = locals[idx - 1]; + if ((prev != null) && prev.getType().isCategory2()) { + locals[idx - 1] = null; + } + } + } + + /** {@inheritDoc} */ + public void set(RegisterSpec spec) { + set(spec.getReg(), spec); + } + + /** {@inheritDoc} */ + public void invalidate(int idx) { + throwIfImmutable(); + locals[idx] = null; + } + + /** {@inheritDoc} */ + public TypeBearer getOrNull(int idx) { + return locals[idx]; + } + + /** {@inheritDoc} */ + public TypeBearer get(int idx) { + TypeBearer result = locals[idx]; + + if (result == null) { + return throwSimException(idx, "invalid"); + } + + return result; + } + + /** {@inheritDoc} */ + public TypeBearer getCategory1(int idx) { + TypeBearer result = get(idx); + Type type = result.getType(); + + if (type.isUninitialized()) { + return throwSimException(idx, "uninitialized instance"); + } + + if (type.isCategory2()) { + return throwSimException(idx, "category-2"); + } + + return result; + } + + /** {@inheritDoc} */ + public TypeBearer getCategory2(int idx) { + TypeBearer result = get(idx); + + if (result.getType().isCategory1()) { + return throwSimException(idx, "category-1"); + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public LocalsArray merge(LocalsArray other) { + if (other instanceof OneLocalsArray) { + return merge((OneLocalsArray)other); + } else { //LocalsArraySet + // LocalsArraySet knows how to merge me. + return other.merge(this); + } + } + + /** + * Merges this OneLocalsArray instance with another OneLocalsArray + * instance. A more-refined version of {@link #merge(LocalsArray) merge} + * which is called by that method when appropriate. + * + * @param other locals array with which to merge + * @return this instance if merge was a no-op, or a new instance if + * the merge resulted in a change. + */ + public OneLocalsArray merge(OneLocalsArray other) { + try { + return Merger.mergeLocals(this, other); + } catch (SimException ex) { + ex.addContext("underlay locals:"); + annotate(ex); + ex.addContext("overlay locals:"); + other.annotate(ex); + throw ex; + } + } + + /** {@inheritDoc} */ + @Override + public LocalsArraySet mergeWithSubroutineCaller + (LocalsArray other, int predLabel) { + + LocalsArraySet result = new LocalsArraySet(getMaxLocals()); + return result.mergeWithSubroutineCaller(other, predLabel); + } + + /**{@inheritDoc}*/ + @Override + protected OneLocalsArray getPrimary() { + return this; + } + + /** + * Throws a properly-formatted exception. + * + * @param idx the salient local index + * @param msg {@code non-null;} useful message + * @return never (keeps compiler happy) + */ + private static TypeBearer throwSimException(int idx, String msg) { + throw new SimException("local " + Hex.u2(idx) + ": " + msg); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/ReturnAddress.java b/dexlib/src/main/java/com/android/dx/cf/code/ReturnAddress.java new file mode 100644 index 000000000..ee36450d1 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/ReturnAddress.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.Hex; + +/** + * Representation of a subroutine return address. In Java verification, + * somewhat counterintuitively, the salient bit of information you need to + * know about a return address is the start address of the subroutine + * being returned from, not the address being returned to, so that's + * what instances of this class hang onto. + */ +public final class ReturnAddress implements TypeBearer { + /** {@code >= 0;} the start address of the subroutine being returned from */ + private final int subroutineAddress; + + /** + * Constructs an instance. + * + * @param subroutineAddress {@code >= 0;} the start address of the + * subroutine being returned from + */ + public ReturnAddress(int subroutineAddress) { + if (subroutineAddress < 0) { + throw new IllegalArgumentException("subroutineAddress < 0"); + } + + this.subroutineAddress = subroutineAddress; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return (""); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toString(); + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.RETURN_ADDRESS; + } + + /** {@inheritDoc} */ + public TypeBearer getFrameType() { + return this; + } + + /** {@inheritDoc} */ + public int getBasicType() { + return Type.RETURN_ADDRESS.getBasicType(); + } + + /** {@inheritDoc} */ + public int getBasicFrameType() { + return Type.RETURN_ADDRESS.getBasicFrameType(); + } + + /** {@inheritDoc} */ + public boolean isConstant() { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof ReturnAddress)) { + return false; + } + + return subroutineAddress == ((ReturnAddress) other).subroutineAddress; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return subroutineAddress; + } + + /** + * Gets the subroutine address. + * + * @return {@code >= 0;} the subroutine address + */ + public int getSubroutineAddress() { + return subroutineAddress; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/Ropper.java b/dexlib/src/main/java/com/android/dx/cf/code/Ropper.java new file mode 100644 index 000000000..12a48f54f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/Ropper.java @@ -0,0 +1,1800 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.cf.iface.MethodList; +import com.android.dx.dex.DexOptions; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.code.BasicBlock; +import com.android.dx.rop.code.BasicBlockList; +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.InsnList; +import com.android.dx.rop.code.PlainCstInsn; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.code.ThrowingCstInsn; +import com.android.dx.rop.code.ThrowingInsn; +import com.android.dx.rop.code.TranslationAdvice; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.Bits; +import com.android.dx.util.Hex; +import com.android.dx.util.IntList; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Utility that converts a basic block list into a list of register-oriented + * blocks. + */ +public final class Ropper { + /** label offset for the parameter assignment block */ + private static final int PARAM_ASSIGNMENT = -1; + + /** label offset for the return block */ + private static final int RETURN = -2; + + /** label offset for the synchronized method final return block */ + private static final int SYNCH_RETURN = -3; + + /** label offset for the first synchronized method setup block */ + private static final int SYNCH_SETUP_1 = -4; + + /** label offset for the second synchronized method setup block */ + private static final int SYNCH_SETUP_2 = -5; + + /** + * label offset for the first synchronized method exception + * handler block + */ + private static final int SYNCH_CATCH_1 = -6; + + /** + * label offset for the second synchronized method exception + * handler block + */ + private static final int SYNCH_CATCH_2 = -7; + + /** number of special label offsets */ + private static final int SPECIAL_LABEL_COUNT = 7; + + /** {@code non-null;} method being converted */ + private final ConcreteMethod method; + + /** {@code non-null;} original block list */ + private final ByteBlockList blocks; + + /** max locals of the method */ + private final int maxLocals; + + /** max label (exclusive) of any original bytecode block */ + private final int maxLabel; + + /** {@code non-null;} simulation machine to use */ + private final RopperMachine machine; + + /** {@code non-null;} simulator to use */ + private final Simulator sim; + + /** + * {@code non-null;} sparse array mapping block labels to initial frame + * contents, if known + */ + private final Frame[] startFrames; + + /** {@code non-null;} output block list in-progress */ + private final ArrayList result; + + /** + * {@code non-null;} list of subroutine-nest labels + * (See {@link Frame#getSubroutines} associated with each result block. + * Parallel to {@link Ropper#result}. + */ + private final ArrayList resultSubroutines; + + /** + * {@code non-null;} for each block (by label) that is used as an exception + * handler in the input, the exception handling info in Rop. + */ + private final CatchInfo[] catchInfos; + + /** + * whether an exception-handler block for a synchronized method was + * ever required + */ + private boolean synchNeedsExceptionHandler; + + /** + * {@code non-null;} list of subroutines indexed by label of start + * address */ + private final Subroutine[] subroutines; + + /** true if {@code subroutines} is non-empty */ + private boolean hasSubroutines; + + /** Allocates labels of exception handler setup blocks. */ + private final ExceptionSetupLabelAllocator exceptionSetupLabelAllocator; + + /** + * Keeps mapping of an input exception handler target code and how it is generated/targeted in + * Rop. + */ + private class CatchInfo { + /** + * {@code non-null;} map of ExceptionHandlerSetup by the type they handle */ + private final Map setups = + new HashMap(); + + /** + * Get the {@link ExceptionHandlerSetup} corresponding to the given type. The + * ExceptionHandlerSetup is created if this the first request for the given type. + * + * @param caughtType {@code non-null;} the type catch by the requested setup + * @return {@code non-null;} the handler setup block info for the given type + */ + ExceptionHandlerSetup getSetup(Type caughtType) { + ExceptionHandlerSetup handler = setups.get(caughtType); + if (handler == null) { + int handlerSetupLabel = exceptionSetupLabelAllocator.getNextLabel(); + handler = new ExceptionHandlerSetup(caughtType, handlerSetupLabel); + setups.put(caughtType, handler); + } + return handler; + } + + /** + * Get all {@link ExceptionHandlerSetup} of this handler. + * + * @return {@code non-null;} + */ + Collection getSetups() { + return setups.values(); + } + } + + /** + * Keeps track of an exception handler setup. + */ + private static class ExceptionHandlerSetup { + /** + * {@code non-null;} The caught type. */ + private Type caughtType; + /** + * {@code >= 0;} The label of the exception setup block. */ + private int label; + + /** + * Constructs instance. + * + * @param caughtType {@code non-null;} the caught type + * @param label {@code >= 0;} the label + */ + ExceptionHandlerSetup(Type caughtType, int label) { + this.caughtType = caughtType; + this.label = label; + } + + /** + * @return {@code non-null;} the caught type + */ + Type getCaughtType() { + return caughtType; + } + + /** + * @return {@code >= 0;} the label + */ + public int getLabel() { + return label; + } + } + + /** + * Keeps track of subroutines that exist in java form and are inlined in + * Rop form. + */ + private class Subroutine { + /** list of all blocks that jsr to this subroutine */ + private BitSet callerBlocks; + /** List of all blocks that return from this subroutine */ + private BitSet retBlocks; + /** first block in this subroutine */ + private int startBlock; + + /** + * Constructs instance. + * + * @param startBlock First block of the subroutine. + */ + Subroutine(int startBlock) { + this.startBlock = startBlock; + retBlocks = new BitSet(maxLabel); + callerBlocks = new BitSet(maxLabel); + hasSubroutines = true; + } + + /** + * Constructs instance. + * + * @param startBlock First block of the subroutine. + * @param retBlock one of the ret blocks (final blocks) of this + * subroutine. + */ + Subroutine(int startBlock, int retBlock) { + this(startBlock); + addRetBlock(retBlock); + } + + /** + * @return {@code >= 0;} the label of the subroutine's start block. + */ + int getStartBlock() { + return startBlock; + } + + /** + * Adds a label to the list of ret blocks (final blocks) for this + * subroutine. + * + * @param retBlock ret block label + */ + void addRetBlock(int retBlock) { + retBlocks.set(retBlock); + } + + /** + * Adds a label to the list of caller blocks for this subroutine. + * + * @param label a block that invokes this subroutine. + */ + void addCallerBlock(int label) { + callerBlocks.set(label); + } + + /** + * Generates a list of subroutine successors. Note: successor blocks + * could be listed more than once. This is ok, because this successor + * list (and the block it's associated with) will be copied and inlined + * before we leave the ropper. Redundent successors will result in + * redundent (no-op) merges. + * + * @return all currently known successors + * (return destinations) for that subroutine + */ + IntList getSuccessors() { + IntList successors = new IntList(callerBlocks.size()); + + /* + * For each subroutine caller, get it's target. If the + * target is us, add the ret target (subroutine successor) + * to our list + */ + + for (int label = callerBlocks.nextSetBit(0); label >= 0; + label = callerBlocks.nextSetBit(label+1)) { + BasicBlock subCaller = labelToBlock(label); + successors.add(subCaller.getSuccessors().get(0)); + } + + successors.setImmutable(); + + return successors; + } + + /** + * Merges the specified frame into this subroutine's successors, + * setting {@code workSet} as appropriate. To be called with + * the frame of a subroutine ret block. + * + * @param frame {@code non-null;} frame from ret block to merge + * @param workSet {@code non-null;} workset to update + */ + void mergeToSuccessors(Frame frame, int[] workSet) { + for (int label = callerBlocks.nextSetBit(0); label >= 0; + label = callerBlocks.nextSetBit(label+1)) { + BasicBlock subCaller = labelToBlock(label); + int succLabel = subCaller.getSuccessors().get(0); + + Frame subFrame = frame.subFrameForLabel(startBlock, label); + + if (subFrame != null) { + mergeAndWorkAsNecessary(succLabel, -1, null, + subFrame, workSet); + } else { + Bits.set(workSet, label); + } + } + } + } + + /** + * Converts a {@link ConcreteMethod} to a {@link RopMethod}. + * + * @param method {@code non-null;} method to convert + * @param advice {@code non-null;} translation advice to use + * @param methods {@code non-null;} list of methods defined by the class + * that defines {@code method}. + * @return {@code non-null;} the converted instance + */ + public static RopMethod convert(ConcreteMethod method, + TranslationAdvice advice, MethodList methods, DexOptions dexOptions) { + try { + Ropper r = new Ropper(method, advice, methods, dexOptions); + r.doit(); + return r.getRopMethod(); + } catch (SimException ex) { + ex.addContext("...while working on method " + + method.getNat().toHuman()); + throw ex; + } + } + + /** + * Constructs an instance. This class is not publicly instantiable; use + * {@link #convert}. + * + * @param method {@code non-null;} method to convert + * @param advice {@code non-null;} translation advice to use + * @param methods {@code non-null;} list of methods defined by the class + * that defines {@code method}. + * @param dexOptions {@code non-null;} options for dex output + */ + private Ropper(ConcreteMethod method, TranslationAdvice advice, MethodList methods, + DexOptions dexOptions) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (advice == null) { + throw new NullPointerException("advice == null"); + } + + this.method = method; + this.blocks = BasicBlocker.identifyBlocks(method); + this.maxLabel = blocks.getMaxLabel(); + this.maxLocals = method.getMaxLocals(); + this.machine = new RopperMachine(this, method, advice, methods); + this.sim = new Simulator(machine, method, dexOptions); + this.startFrames = new Frame[maxLabel]; + this.subroutines = new Subroutine[maxLabel]; + + /* + * The "* 2 + 10" below is to conservatively believe that every + * block is an exception handler target and should also + * take care of enough other possible extra overhead such that + * the underlying array is unlikely to need resizing. + */ + this.result = new ArrayList(blocks.size() * 2 + 10); + this.resultSubroutines = + new ArrayList(blocks.size() * 2 + 10); + + this.catchInfos = new CatchInfo[maxLabel]; + this.synchNeedsExceptionHandler = false; + + /* + * Set up the first stack frame with the right limits, but leave it + * empty here (to be filled in outside of the constructor). + */ + startFrames[0] = new Frame(maxLocals, method.getMaxStack()); + exceptionSetupLabelAllocator = new ExceptionSetupLabelAllocator(); + } + + /** + * Gets the first (lowest) register number to use as the temporary + * area when unwinding stack manipulation ops. + * + * @return {@code >= 0;} the first register to use + */ + /*package*/ int getFirstTempStackReg() { + /* + * We use the register that is just past the deepest possible + * stack element, plus one if the method is synchronized to + * avoid overlapping with the synch register. We don't need to + * do anything else special at this level, since later passes + * will merely notice the highest register used by explicit + * inspection. + */ + int regCount = getNormalRegCount(); + return isSynchronized() ? regCount + 1 : regCount; + } + + /** + * Gets the label for the given special-purpose block. The given label + * should be one of the static constants defined by this class. + * + * @param label {@code < 0;} the special label constant + * @return {@code >= 0;} the actual label value to use + */ + private int getSpecialLabel(int label) { + /* + * The label is bitwise-complemented so that mistakes where + * LABEL is used instead of getSpecialLabel(LABEL) cause a + * failure at block construction time, since negative labels + * are illegal. 0..maxLabel (exclusive) are the original blocks and + * maxLabel..(maxLabel + method.getCatches().size()) are reserved for exception handler + * setup blocks (see getAvailableLabel(), exceptionSetupLabelAllocator). + */ + return maxLabel + method.getCatches().size() + ~label; + } + + /** + * Gets the minimum label for unreserved use. + * + * @return {@code >= 0;} the minimum label + */ + private int getMinimumUnreservedLabel() { + /* + * The labels below (maxLabel + method.getCatches().size() + SPECIAL_LABEL_COUNT) are + * reserved for particular uses. + */ + + return maxLabel + method.getCatches().size() + SPECIAL_LABEL_COUNT; + } + + /** + * Gets an unreserved and available label. + * Labels are distributed this way: + *
    + *
  • [0, maxLabel[ are the labels of the blocks directly + * corresponding to the input bytecode.
  • + *
  • [maxLabel, maxLabel + method.getCatches().size()[ are reserved for exception setup + * blocks.
  • + *
  • [maxLabel + method.getCatches().size(), + * maxLabel + method.getCatches().size() + SPECIAL_LABEL_COUNT[ are reserved for special blocks, + * ie param assignement, return and synch blocks.
  • + *
  • [maxLabel method.getCatches().size() + SPECIAL_LABEL_COUNT, getAvailableLabel()[ assigned + * labels. Note that some + * of the assigned labels may not be used any more if they were assigned to a block that was + * deleted since.
  • + *
+ * + * @return {@code >= 0;} an available label with the guaranty that all greater labels are + * also available. + */ + private int getAvailableLabel() { + int candidate = getMinimumUnreservedLabel(); + + for (BasicBlock bb : result) { + int label = bb.getLabel(); + if (label >= candidate) { + candidate = label + 1; + } + } + + return candidate; + } + + /** + * Gets whether the method being translated is synchronized. + * + * @return whether the method being translated is synchronized + */ + private boolean isSynchronized() { + int accessFlags = method.getAccessFlags(); + return (accessFlags & AccessFlags.ACC_SYNCHRONIZED) != 0; + } + + /** + * Gets whether the method being translated is static. + * + * @return whether the method being translated is static + */ + private boolean isStatic() { + int accessFlags = method.getAccessFlags(); + return (accessFlags & AccessFlags.ACC_STATIC) != 0; + } + + /** + * Gets the total number of registers used for "normal" purposes (i.e., + * for the straightforward translation from the original Java). + * + * @return {@code >= 0;} the total number of registers used + */ + private int getNormalRegCount() { + return maxLocals + method.getMaxStack(); + } + + /** + * Gets the register spec to use to hold the object to synchronize on, + * for a synchronized method. + * + * @return {@code non-null;} the register spec + */ + private RegisterSpec getSynchReg() { + /* + * We use the register that is just past the deepest possible + * stack element, with a minimum of v1 since v0 is what's + * always used to hold the caught exception when unwinding. We + * don't need to do anything else special at this level, since + * later passes will merely notice the highest register used + * by explicit inspection. + */ + int reg = getNormalRegCount(); + return RegisterSpec.make((reg < 1) ? 1 : reg, Type.OBJECT); + } + + /** + * Searches {@link #result} for a block with the given label. Returns its + * index if found, or returns {@code -1} if there is no such block. + * + * @param label the label to look for + * @return {@code >= -1;} the index for the block with the given label or + * {@code -1} if there is no such block + */ + private int labelToResultIndex(int label) { + int sz = result.size(); + for (int i = 0; i < sz; i++) { + BasicBlock one = result.get(i); + if (one.getLabel() == label) { + return i; + } + } + + return -1; + } + + /** + * Searches {@link #result} for a block with the given label. Returns it if + * found, or throws an exception if there is no such block. + * + * @param label the label to look for + * @return {@code non-null;} the block with the given label + */ + private BasicBlock labelToBlock(int label) { + int idx = labelToResultIndex(label); + + if (idx < 0) { + throw new IllegalArgumentException("no such label " + + Hex.u2(label)); + } + + return result.get(idx); + } + + /** + * Adds a block to the output result. + * + * @param block {@code non-null;} the block to add + * @param subroutines {@code non-null;} subroutine label list + * as described in {@link Frame#getSubroutines} + */ + private void addBlock(BasicBlock block, IntList subroutines) { + if (block == null) { + throw new NullPointerException("block == null"); + } + + result.add(block); + subroutines.throwIfMutable(); + resultSubroutines.add(subroutines); + } + + /** + * Adds or replace a block in the output result. If this is a + * replacement, then any extra blocks that got added with the + * original get removed as a result of calling this method. + * + * @param block {@code non-null;} the block to add or replace + * @param subroutines {@code non-null;} subroutine label list + * as described in {@link Frame#getSubroutines} + * @return {@code true} if the block was replaced or + * {@code false} if it was added for the first time + */ + private boolean addOrReplaceBlock(BasicBlock block, IntList subroutines) { + if (block == null) { + throw new NullPointerException("block == null"); + } + + int idx = labelToResultIndex(block.getLabel()); + boolean ret; + + if (idx < 0) { + ret = false; + } else { + /* + * We are replacing a pre-existing block, so find any + * blocks that got added as part of the original and + * remove those too. Such blocks are (possibly indirect) + * successors of this block which are out of the range of + * normally-translated blocks. + */ + removeBlockAndSpecialSuccessors(idx); + ret = true; + } + + result.add(block); + subroutines.throwIfMutable(); + resultSubroutines.add(subroutines); + return ret; + } + + /** + * Adds or replaces a block in the output result. Do not delete + * any successors. + * + * @param block {@code non-null;} the block to add or replace + * @param subroutines {@code non-null;} subroutine label list + * as described in {@link Frame#getSubroutines} + * @return {@code true} if the block was replaced or + * {@code false} if it was added for the first time + */ + private boolean addOrReplaceBlockNoDelete(BasicBlock block, + IntList subroutines) { + if (block == null) { + throw new NullPointerException("block == null"); + } + + int idx = labelToResultIndex(block.getLabel()); + boolean ret; + + if (idx < 0) { + ret = false; + } else { + result.remove(idx); + resultSubroutines.remove(idx); + ret = true; + } + + result.add(block); + subroutines.throwIfMutable(); + resultSubroutines.add(subroutines); + return ret; + } + + /** + * Helper for {@link #addOrReplaceBlock} which recursively removes + * the given block and all blocks that are (direct and indirect) + * successors of it whose labels indicate that they are not in the + * normally-translated range. + * + * @param idx {@code non-null;} block to remove (etc.) + */ + private void removeBlockAndSpecialSuccessors(int idx) { + int minLabel = getMinimumUnreservedLabel(); + BasicBlock block = result.get(idx); + IntList successors = block.getSuccessors(); + int sz = successors.size(); + + result.remove(idx); + resultSubroutines.remove(idx); + + for (int i = 0; i < sz; i++) { + int label = successors.get(i); + if (label >= minLabel) { + idx = labelToResultIndex(label); + if (idx < 0) { + throw new RuntimeException("Invalid label " + + Hex.u2(label)); + } + removeBlockAndSpecialSuccessors(idx); + } + } + } + + /** + * Extracts the resulting {@link RopMethod} from the instance. + * + * @return {@code non-null;} the method object + */ + private RopMethod getRopMethod() { + + // Construct the final list of blocks. + + int sz = result.size(); + BasicBlockList bbl = new BasicBlockList(sz); + for (int i = 0; i < sz; i++) { + bbl.set(i, result.get(i)); + } + bbl.setImmutable(); + + // Construct the method object to wrap it all up. + + /* + * Note: The parameter assignment block is always the first + * that should be executed, hence the second argument to the + * constructor. + */ + return new RopMethod(bbl, getSpecialLabel(PARAM_ASSIGNMENT)); + } + + /** + * Does the conversion. + */ + private void doit() { + int[] workSet = Bits.makeBitSet(maxLabel); + + Bits.set(workSet, 0); + addSetupBlocks(); + setFirstFrame(); + + for (;;) { + int offset = Bits.findFirst(workSet, 0); + if (offset < 0) { + break; + } + Bits.clear(workSet, offset); + ByteBlock block = blocks.labelToBlock(offset); + Frame frame = startFrames[offset]; + try { + processBlock(block, frame, workSet); + } catch (SimException ex) { + ex.addContext("...while working on block " + Hex.u2(offset)); + throw ex; + } + } + + addReturnBlock(); + addSynchExceptionHandlerBlock(); + addExceptionSetupBlocks(); + + if (hasSubroutines) { + // Subroutines are very rare, so skip this step if it's n/a + inlineSubroutines(); + } + } + + /** + * Sets up the first frame to contain all the incoming parameters in + * locals. + */ + private void setFirstFrame() { + Prototype desc = method.getEffectiveDescriptor(); + startFrames[0].initializeWithParameters(desc.getParameterTypes()); + startFrames[0].setImmutable(); + } + + /** + * Processes the given block. + * + * @param block {@code non-null;} block to process + * @param frame {@code non-null;} start frame for the block + * @param workSet {@code non-null;} bits representing work to do, + * which this method may add to + */ + private void processBlock(ByteBlock block, Frame frame, int[] workSet) { + // Prepare the list of caught exceptions for this block. + ByteCatchList catches = block.getCatches(); + machine.startBlock(catches.toRopCatchList()); + + /* + * Using a copy of the given frame, simulate each instruction, + * calling into machine for each. + */ + frame = frame.copy(); + sim.simulate(block, frame); + frame.setImmutable(); + + int extraBlockCount = machine.getExtraBlockCount(); + ArrayList insns = machine.getInsns(); + int insnSz = insns.size(); + + /* + * Merge the frame into each possible non-exceptional + * successor. + */ + + int catchSz = catches.size(); + IntList successors = block.getSuccessors(); + + int startSuccessorIndex; + + Subroutine calledSubroutine = null; + if (machine.hasJsr()) { + /* + * If this frame ends in a JSR, only merge our frame with + * the subroutine start, not the subroutine's return target. + */ + startSuccessorIndex = 1; + + int subroutineLabel = successors.get(1); + + if (subroutines[subroutineLabel] == null) { + subroutines[subroutineLabel] = + new Subroutine (subroutineLabel); + } + + subroutines[subroutineLabel].addCallerBlock(block.getLabel()); + + calledSubroutine = subroutines[subroutineLabel]; + } else if (machine.hasRet()) { + /* + * This block ends in a ret, which means it's the final block + * in some subroutine. Ultimately, this block will be copied + * and inlined for each call and then disposed of. + */ + + ReturnAddress ra = machine.getReturnAddress(); + int subroutineLabel = ra.getSubroutineAddress(); + + if (subroutines[subroutineLabel] == null) { + subroutines[subroutineLabel] + = new Subroutine (subroutineLabel, block.getLabel()); + } else { + subroutines[subroutineLabel].addRetBlock(block.getLabel()); + } + + successors = subroutines[subroutineLabel].getSuccessors(); + subroutines[subroutineLabel] + .mergeToSuccessors(frame, workSet); + // Skip processing below since we just did it. + startSuccessorIndex = successors.size(); + } else if (machine.wereCatchesUsed()) { + /* + * If there are catches, then the first successors + * (which will either be all of them or all but the last one) + * are catch targets. + */ + startSuccessorIndex = catchSz; + } else { + startSuccessorIndex = 0; + } + + int succSz = successors.size(); + for (int i = startSuccessorIndex; i < succSz; + i++) { + int succ = successors.get(i); + try { + mergeAndWorkAsNecessary(succ, block.getLabel(), + calledSubroutine, frame, workSet); + } catch (SimException ex) { + ex.addContext("...while merging to block " + Hex.u2(succ)); + throw ex; + } + } + + if ((succSz == 0) && machine.returns()) { + /* + * The block originally contained a return, but it has + * been made to instead end with a goto, and we need to + * tell it at this point that its sole successor is the + * return block. This has to happen after the merge loop + * above, since, at this point, the return block doesn't + * actually exist; it gets synthesized at the end of + * processing the original blocks. + */ + successors = IntList.makeImmutable(getSpecialLabel(RETURN)); + succSz = 1; + } + + int primarySucc; + + if (succSz == 0) { + primarySucc = -1; + } else { + primarySucc = machine.getPrimarySuccessorIndex(); + if (primarySucc >= 0) { + primarySucc = successors.get(primarySucc); + } + } + + /* + * This variable is true only when the method is synchronized and + * the block being processed can possibly throw an exception. + */ + boolean synch = isSynchronized() && machine.canThrow(); + + if (synch || (catchSz != 0)) { + /* + * Deal with exception handlers: Merge an exception-catch + * frame into each possible exception handler, and + * construct a new set of successors to point at the + * exception handler setup blocks (which get synthesized + * at the very end of processing). + */ + boolean catchesAny = false; + IntList newSucc = new IntList(succSz); + for (int i = 0; i < catchSz; i++) { + ByteCatchList.Item one = catches.get(i); + CstType exceptionClass = one.getExceptionClass(); + int targ = one.getHandlerPc(); + + catchesAny |= (exceptionClass == CstType.OBJECT); + + Frame f = frame.makeExceptionHandlerStartFrame(exceptionClass); + + try { + mergeAndWorkAsNecessary(targ, block.getLabel(), + null, f, workSet); + } catch (SimException ex) { + ex.addContext("...while merging exception to block " + + Hex.u2(targ)); + throw ex; + } + + /* + * Set up the exception handler type. + */ + CatchInfo handlers = catchInfos[targ]; + if (handlers == null) { + handlers = new CatchInfo(); + catchInfos[targ] = handlers; + } + ExceptionHandlerSetup handler = handlers.getSetup(exceptionClass.getClassType()); + + /* + * The synthesized exception setup block will have the label given by handler. + */ + newSucc.add(handler.getLabel()); + } + + if (synch && !catchesAny) { + /* + * The method is synchronized and this block doesn't + * already have a catch-all handler, so add one to the + * end, both in the successors and in the throwing + * instruction(s) at the end of the block (which is where + * the caught classes live). + */ + newSucc.add(getSpecialLabel(SYNCH_CATCH_1)); + synchNeedsExceptionHandler = true; + + for (int i = insnSz - extraBlockCount - 1; i < insnSz; i++) { + Insn insn = insns.get(i); + if (insn.canThrow()) { + insn = insn.withAddedCatch(Type.OBJECT); + insns.set(i, insn); + } + } + } + + if (primarySucc >= 0) { + newSucc.add(primarySucc); + } + + newSucc.setImmutable(); + successors = newSucc; + } + + // Construct the final resulting block(s), and store it (them). + + int primarySuccListIndex = successors.indexOf(primarySucc); + + /* + * If there are any extra blocks, work backwards through the + * list of instructions, adding single-instruction blocks, and + * resetting the successors variables as appropriate. + */ + for (/*extraBlockCount*/; extraBlockCount > 0; extraBlockCount--) { + /* + * Some of the blocks that the RopperMachine wants added + * are for move-result insns, and these need goto insns as well. + */ + Insn extraInsn = insns.get(--insnSz); + boolean needsGoto + = extraInsn.getOpcode().getBranchingness() + == Rop.BRANCH_NONE; + InsnList il = new InsnList(needsGoto ? 2 : 1); + IntList extraBlockSuccessors = successors; + + il.set(0, extraInsn); + + if (needsGoto) { + il.set(1, new PlainInsn(Rops.GOTO, + extraInsn.getPosition(), null, + RegisterSpecList.EMPTY)); + /* + * Obviously, this block won't be throwing an exception + * so it should only have one successor. + */ + extraBlockSuccessors = IntList.makeImmutable(primarySucc); + } + il.setImmutable(); + + int label = getAvailableLabel(); + BasicBlock bb = new BasicBlock(label, il, extraBlockSuccessors, + primarySucc); + // All of these extra blocks will be in the same subroutine + addBlock(bb, frame.getSubroutines()); + + successors = successors.mutableCopy(); + successors.set(primarySuccListIndex, label); + successors.setImmutable(); + primarySucc = label; + } + + Insn lastInsn = (insnSz == 0) ? null : insns.get(insnSz - 1); + + /* + * Add a goto to the end of the block if it doesn't already + * end with a branch, to maintain the invariant that all + * blocks end with a branch of some sort or other. Note that + * it is possible for there to be blocks for which no + * instructions were ever output (e.g., only consist of pop* + * in the original Java bytecode). + */ + if ((lastInsn == null) || + (lastInsn.getOpcode().getBranchingness() == Rop.BRANCH_NONE)) { + SourcePosition pos = (lastInsn == null) ? SourcePosition.NO_INFO : + lastInsn.getPosition(); + insns.add(new PlainInsn(Rops.GOTO, pos, null, + RegisterSpecList.EMPTY)); + insnSz++; + } + + /* + * Construct a block for the remaining instructions (which in + * the usual case is all of them). + */ + + InsnList il = new InsnList(insnSz); + for (int i = 0; i < insnSz; i++) { + il.set(i, insns.get(i)); + } + il.setImmutable(); + + BasicBlock bb = + new BasicBlock(block.getLabel(), il, successors, primarySucc); + addOrReplaceBlock(bb, frame.getSubroutines()); + } + + /** + * Helper for {@link #processBlock}, which merges frames and + * adds to the work set, as necessary. + * + * @param label {@code >= 0;} label to work on + * @param pred predecessor label; must be {@code >= 0} when + * {@code label} is a subroutine start block and calledSubroutine + * is non-null. Otherwise, may be -1. + * @param calledSubroutine {@code null-ok;} a Subroutine instance if + * {@code label} is the first block in a subroutine. + * @param frame {@code non-null;} new frame for the labelled block + * @param workSet {@code non-null;} bits representing work to do, + * which this method may add to + */ + private void mergeAndWorkAsNecessary(int label, int pred, + Subroutine calledSubroutine, Frame frame, int[] workSet) { + Frame existing = startFrames[label]; + Frame merged; + + if (existing != null) { + /* + * Some other block also continues at this label. Merge + * the frames, and re-set the bit in the work set if there + * was a change. + */ + if (calledSubroutine != null) { + merged = existing.mergeWithSubroutineCaller(frame, + calledSubroutine.getStartBlock(), pred); + } else { + merged = existing.mergeWith(frame); + } + if (merged != existing) { + startFrames[label] = merged; + Bits.set(workSet, label); + } + } else { + // This is the first time this label has been encountered. + if (calledSubroutine != null) { + startFrames[label] + = frame.makeNewSubroutineStartFrame(label, pred); + } else { + startFrames[label] = frame; + } + Bits.set(workSet, label); + } + } + + /** + * Constructs and adds the blocks that perform setup for the rest of + * the method. This includes a first block which merely contains + * assignments from parameters to the same-numbered registers and + * a possible second block which deals with synchronization. + */ + private void addSetupBlocks() { + LocalVariableList localVariables = method.getLocalVariables(); + SourcePosition pos = method.makeSourcePosistion(0); + Prototype desc = method.getEffectiveDescriptor(); + StdTypeList params = desc.getParameterTypes(); + int sz = params.size(); + InsnList insns = new InsnList(sz + 1); + int at = 0; + + for (int i = 0; i < sz; i++) { + Type one = params.get(i); + LocalVariableList.Item local = + localVariables.pcAndIndexToLocal(0, at); + RegisterSpec result = (local == null) ? + RegisterSpec.make(at, one) : + RegisterSpec.makeLocalOptional(at, one, local.getLocalItem()); + + Insn insn = new PlainCstInsn(Rops.opMoveParam(one), pos, result, + RegisterSpecList.EMPTY, + CstInteger.make(at)); + insns.set(i, insn); + at += one.getCategory(); + } + + insns.set(sz, new PlainInsn(Rops.GOTO, pos, null, + RegisterSpecList.EMPTY)); + insns.setImmutable(); + + boolean synch = isSynchronized(); + int label = synch ? getSpecialLabel(SYNCH_SETUP_1) : 0; + BasicBlock bb = + new BasicBlock(getSpecialLabel(PARAM_ASSIGNMENT), insns, + IntList.makeImmutable(label), label); + addBlock(bb, IntList.EMPTY); + + if (synch) { + RegisterSpec synchReg = getSynchReg(); + Insn insn; + if (isStatic()) { + insn = new ThrowingCstInsn(Rops.CONST_OBJECT, pos, + RegisterSpecList.EMPTY, + StdTypeList.EMPTY, + method.getDefiningClass()); + insns = new InsnList(1); + insns.set(0, insn); + } else { + insns = new InsnList(2); + insn = new PlainCstInsn(Rops.MOVE_PARAM_OBJECT, pos, + synchReg, RegisterSpecList.EMPTY, + CstInteger.VALUE_0); + insns.set(0, insn); + insns.set(1, new PlainInsn(Rops.GOTO, pos, null, + RegisterSpecList.EMPTY)); + } + + int label2 = getSpecialLabel(SYNCH_SETUP_2); + insns.setImmutable(); + bb = new BasicBlock(label, insns, + IntList.makeImmutable(label2), label2); + addBlock(bb, IntList.EMPTY); + + insns = new InsnList(isStatic() ? 2 : 1); + + if (isStatic()) { + insns.set(0, new PlainInsn(Rops.opMoveResultPseudo(synchReg), + pos, synchReg, RegisterSpecList.EMPTY)); + } + + insn = new ThrowingInsn(Rops.MONITOR_ENTER, pos, + RegisterSpecList.make(synchReg), + StdTypeList.EMPTY); + insns.set(isStatic() ? 1 :0, insn); + insns.setImmutable(); + bb = new BasicBlock(label2, insns, IntList.makeImmutable(0), 0); + addBlock(bb, IntList.EMPTY); + } + } + + /** + * Constructs and adds the return block, if necessary. The return + * block merely contains an appropriate {@code return} + * instruction. + */ + private void addReturnBlock() { + Rop returnOp = machine.getReturnOp(); + + if (returnOp == null) { + /* + * The method being converted never returns normally, so there's + * no need for a return block. + */ + return; + } + + SourcePosition returnPos = machine.getReturnPosition(); + int label = getSpecialLabel(RETURN); + + if (isSynchronized()) { + InsnList insns = new InsnList(1); + Insn insn = new ThrowingInsn(Rops.MONITOR_EXIT, returnPos, + RegisterSpecList.make(getSynchReg()), + StdTypeList.EMPTY); + insns.set(0, insn); + insns.setImmutable(); + + int nextLabel = getSpecialLabel(SYNCH_RETURN); + BasicBlock bb = + new BasicBlock(label, insns, + IntList.makeImmutable(nextLabel), nextLabel); + addBlock(bb, IntList.EMPTY); + + label = nextLabel; + } + + InsnList insns = new InsnList(1); + TypeList sourceTypes = returnOp.getSources(); + RegisterSpecList sources; + + if (sourceTypes.size() == 0) { + sources = RegisterSpecList.EMPTY; + } else { + RegisterSpec source = RegisterSpec.make(0, sourceTypes.getType(0)); + sources = RegisterSpecList.make(source); + } + + Insn insn = new PlainInsn(returnOp, returnPos, null, sources); + insns.set(0, insn); + insns.setImmutable(); + + BasicBlock bb = new BasicBlock(label, insns, IntList.EMPTY, -1); + addBlock(bb, IntList.EMPTY); + } + + /** + * Constructs and adds, if necessary, the catch-all exception handler + * block to deal with unwinding the lock taken on entry to a synchronized + * method. + */ + private void addSynchExceptionHandlerBlock() { + if (!synchNeedsExceptionHandler) { + /* + * The method being converted either isn't synchronized or + * can't possibly throw exceptions in its main body, so + * there's no need for a synchronized method exception + * handler. + */ + return; + } + + SourcePosition pos = method.makeSourcePosistion(0); + RegisterSpec exReg = RegisterSpec.make(0, Type.THROWABLE); + BasicBlock bb; + Insn insn; + + InsnList insns = new InsnList(2); + insn = new PlainInsn(Rops.opMoveException(Type.THROWABLE), pos, + exReg, RegisterSpecList.EMPTY); + insns.set(0, insn); + insn = new ThrowingInsn(Rops.MONITOR_EXIT, pos, + RegisterSpecList.make(getSynchReg()), + StdTypeList.EMPTY); + insns.set(1, insn); + insns.setImmutable(); + + int label2 = getSpecialLabel(SYNCH_CATCH_2); + bb = new BasicBlock(getSpecialLabel(SYNCH_CATCH_1), insns, + IntList.makeImmutable(label2), label2); + addBlock(bb, IntList.EMPTY); + + insns = new InsnList(1); + insn = new ThrowingInsn(Rops.THROW, pos, + RegisterSpecList.make(exReg), + StdTypeList.EMPTY); + insns.set(0, insn); + insns.setImmutable(); + + bb = new BasicBlock(label2, insns, IntList.EMPTY, -1); + addBlock(bb, IntList.EMPTY); + } + + /** + * Creates the exception handler setup blocks. "maxLocals" + * below is because that's the register number corresponding + * to the sole element on a one-deep stack (which is the + * situation at the start of an exception handler block). + */ + private void addExceptionSetupBlocks() { + + int len = catchInfos.length; + for (int i = 0; i < len; i++) { + CatchInfo catches = catchInfos[i]; + if (catches != null) { + for (ExceptionHandlerSetup one : catches.getSetups()) { + Insn proto = labelToBlock(i).getFirstInsn(); + SourcePosition pos = proto.getPosition(); + InsnList il = new InsnList(2); + + Insn insn = new PlainInsn(Rops.opMoveException(one.getCaughtType()), + pos, + RegisterSpec.make(maxLocals, one.getCaughtType()), + RegisterSpecList.EMPTY); + il.set(0, insn); + + insn = new PlainInsn(Rops.GOTO, pos, null, + RegisterSpecList.EMPTY); + il.set(1, insn); + il.setImmutable(); + + BasicBlock bb = new BasicBlock(one.getLabel(), + il, + IntList.makeImmutable(i), + i); + addBlock(bb, startFrames[i].getSubroutines()); + } + } + } + } + + /** + * Checks to see if the basic block is a subroutine caller block. + * + * @param bb {@code non-null;} the basic block in question + * @return true if this block calls a subroutine + */ + private boolean isSubroutineCaller(BasicBlock bb) { + IntList successors = bb.getSuccessors(); + if (successors.size() < 2) return false; + + int subLabel = successors.get(1); + + return (subLabel < subroutines.length) + && (subroutines[subLabel] != null); + } + + /** + * Inlines any subroutine calls. + */ + private void inlineSubroutines() { + final IntList reachableSubroutineCallerLabels = new IntList(4); + + /* + * Compile a list of all subroutine calls reachable + * through the normal (non-subroutine) flow. We do this first, since + * we'll be affecting the call flow as we go. + * + * Start at label 0 -- the param assignment block has nothing for us + */ + forEachNonSubBlockDepthFirst(0, new BasicBlock.Visitor() { + public void visitBlock(BasicBlock b) { + if (isSubroutineCaller(b)) { + reachableSubroutineCallerLabels.add(b.getLabel()); + } + } + }); + + /* + * Convert the resultSubroutines list, indexed by block index, + * to a label-to-subroutines mapping used by the inliner. + */ + int largestAllocedLabel = getAvailableLabel(); + ArrayList labelToSubroutines + = new ArrayList(largestAllocedLabel); + for (int i = 0; i < largestAllocedLabel; i++) { + labelToSubroutines.add(null); + } + + for (int i = 0; i < result.size(); i++) { + BasicBlock b = result.get(i); + if (b == null) { + continue; + } + IntList subroutineList = resultSubroutines.get(i); + labelToSubroutines.set(b.getLabel(), subroutineList); + } + + /* + * Inline all reachable subroutines. + * Inner subroutines will be inlined as they are encountered. + */ + int sz = reachableSubroutineCallerLabels.size(); + for (int i = 0 ; i < sz ; i++) { + int label = reachableSubroutineCallerLabels.get(i); + new SubroutineInliner( + new LabelAllocator(getAvailableLabel()), + labelToSubroutines) + .inlineSubroutineCalledFrom(labelToBlock(label)); + } + + // Now find the blocks that aren't reachable and remove them + deleteUnreachableBlocks(); + } + + /** + * Deletes all blocks that cannot be reached. This is run to delete + * original subroutine blocks after subroutine inlining. + */ + private void deleteUnreachableBlocks() { + final IntList reachableLabels = new IntList(result.size()); + + // subroutine inlining is done now and we won't update this list here + resultSubroutines.clear(); + + forEachNonSubBlockDepthFirst(getSpecialLabel(PARAM_ASSIGNMENT), + new BasicBlock.Visitor() { + + public void visitBlock(BasicBlock b) { + reachableLabels.add(b.getLabel()); + } + }); + + reachableLabels.sort(); + + for (int i = result.size() - 1 ; i >= 0 ; i--) { + if (reachableLabels.indexOf(result.get(i).getLabel()) < 0) { + result.remove(i); + // unnecessary here really, since subroutine inlining is done + //resultSubroutines.remove(i); + } + } + } + + /** + * Allocates labels, without requiring previously allocated labels + * to have been added to the blocks list. + */ + private static class LabelAllocator { + int nextAvailableLabel; + + /** + * @param startLabel available label to start allocating from + */ + LabelAllocator(int startLabel) { + nextAvailableLabel = startLabel; + } + + /** + * @return next available label + */ + int getNextLabel() { + return nextAvailableLabel++; + } + } + + /** + * Allocates labels for exception setup blocks. + */ + private class ExceptionSetupLabelAllocator extends LabelAllocator { + int maxSetupLabel; + + ExceptionSetupLabelAllocator() { + super(maxLabel); + maxSetupLabel = maxLabel + method.getCatches().size(); + } + + @Override + int getNextLabel() { + if (nextAvailableLabel >= maxSetupLabel) { + throw new IndexOutOfBoundsException(); + } + return nextAvailableLabel ++; + } + } + + /** + * Inlines a subroutine. Start by calling + * {@link #inlineSubroutineCalledFrom}. + */ + private class SubroutineInliner { + /** + * maps original label to the label that will be used by the + * inlined version + */ + private final HashMap origLabelToCopiedLabel; + + /** set of original labels that need to be copied */ + private final BitSet workList; + + /** the label of the original start block for this subroutine */ + private int subroutineStart; + + /** the label of the ultimate return block */ + private int subroutineSuccessor; + + /** used for generating new labels for copied blocks */ + private final LabelAllocator labelAllocator; + + /** + * A mapping, indexed by label, to subroutine nesting list. + * The subroutine nest list is as returned by + * {@link Frame#getSubroutines}. + */ + private final ArrayList labelToSubroutines; + + SubroutineInliner(final LabelAllocator labelAllocator, + ArrayList labelToSubroutines) { + origLabelToCopiedLabel = new HashMap(); + + workList = new BitSet(maxLabel); + + this.labelAllocator = labelAllocator; + this.labelToSubroutines = labelToSubroutines; + } + + /** + * Inlines a subroutine. + * + * @param b block where {@code jsr} occurred in the original bytecode + */ + void inlineSubroutineCalledFrom(final BasicBlock b) { + /* + * The 0th successor of a subroutine caller block is where + * the subroutine should return to. The 1st successor is + * the start block of the subroutine. + */ + subroutineSuccessor = b.getSuccessors().get(0); + subroutineStart = b.getSuccessors().get(1); + + /* + * This allocates an initial label and adds the first + * block to the worklist. + */ + int newSubStartLabel = mapOrAllocateLabel(subroutineStart); + + for (int label = workList.nextSetBit(0); label >= 0; + label = workList.nextSetBit(0)) { + workList.clear(label); + int newLabel = origLabelToCopiedLabel.get(label); + + copyBlock(label, newLabel); + + if (isSubroutineCaller(labelToBlock(label))) { + new SubroutineInliner(labelAllocator, labelToSubroutines) + .inlineSubroutineCalledFrom(labelToBlock(newLabel)); + } + } + + /* + * Replace the original caller block, since we now have a + * new successor + */ + + addOrReplaceBlockNoDelete( + new BasicBlock(b.getLabel(), b.getInsns(), + IntList.makeImmutable (newSubStartLabel), + newSubStartLabel), + labelToSubroutines.get(b.getLabel())); + } + + /** + * Copies a basic block, mapping its successors along the way. + * + * @param origLabel original block label + * @param newLabel label that the new block should have + */ + private void copyBlock(int origLabel, int newLabel) { + + BasicBlock origBlock = labelToBlock(origLabel); + + final IntList origSuccessors = origBlock.getSuccessors(); + IntList successors; + int primarySuccessor = -1; + Subroutine subroutine; + + if (isSubroutineCaller(origBlock)) { + /* + * A subroutine call inside a subroutine call. + * Set up so we can recurse. The caller block should have + * it's first successor be a copied block that will be + * the subroutine's return point. It's second successor will + * be copied when we recurse, and remains as the original + * label of the start of the inner subroutine. + */ + + successors = IntList.makeImmutable( + mapOrAllocateLabel(origSuccessors.get(0)), + origSuccessors.get(1)); + // primary successor will be set when this block is replaced + } else if (null + != (subroutine = subroutineFromRetBlock(origLabel))) { + /* + * this is a ret block -- its successor + * should be subroutineSuccessor + */ + + // Sanity check + if (subroutine.startBlock != subroutineStart) { + throw new RuntimeException ( + "ret instruction returns to label " + + Hex.u2 (subroutine.startBlock) + + " expected: " + Hex.u2(subroutineStart)); + } + + successors = IntList.makeImmutable(subroutineSuccessor); + primarySuccessor = subroutineSuccessor; + } else { + // Map all the successor labels + + int origPrimary = origBlock.getPrimarySuccessor(); + int sz = origSuccessors.size(); + + successors = new IntList(sz); + + for (int i = 0 ; i < sz ; i++) { + int origSuccLabel = origSuccessors.get(i); + int newSuccLabel = mapOrAllocateLabel(origSuccLabel); + + successors.add(newSuccLabel); + + if (origPrimary == origSuccLabel) { + primarySuccessor = newSuccLabel; + } + } + + successors.setImmutable(); + } + + addBlock ( + new BasicBlock(newLabel, + filterMoveReturnAddressInsns(origBlock.getInsns()), + successors, primarySuccessor), + labelToSubroutines.get(newLabel)); + } + + /** + * Checks to see if a specified label is involved in a specified + * subroutine. + * + * @param label {@code >= 0;} a basic block label + * @param subroutineStart {@code >= 0;} a subroutine as identified + * by the label of its start block + * @return true if the block is dominated by the subroutine call + */ + private boolean involvedInSubroutine(int label, int subroutineStart) { + IntList subroutinesList = labelToSubroutines.get(label); + return (subroutinesList != null && subroutinesList.size() > 0 + && subroutinesList.top() == subroutineStart); + } + + /** + * Maps the label of a pre-copied block to the label of the inlined + * block, allocating a new label and adding it to the worklist + * if necessary. If the origLabel is a "special" label, it + * is returned exactly and not scheduled for duplication: copying + * never proceeds past a special label, which likely is the function + * return block or an immediate predecessor. + * + * @param origLabel label of original, pre-copied block + * @return label for new, inlined block + */ + private int mapOrAllocateLabel(int origLabel) { + int resultLabel; + Integer mappedLabel = origLabelToCopiedLabel.get(origLabel); + + if (mappedLabel != null) { + resultLabel = mappedLabel; + } else if (!involvedInSubroutine(origLabel,subroutineStart)) { + /* + * A subroutine has ended by some means other than a "ret" + * (which really means a throw caught later). + */ + resultLabel = origLabel; + } else { + resultLabel = labelAllocator.getNextLabel(); + workList.set(origLabel); + origLabelToCopiedLabel.put(origLabel, resultLabel); + + // The new label has the same frame as the original label + while (labelToSubroutines.size() <= resultLabel) { + labelToSubroutines.add(null); + } + labelToSubroutines.set(resultLabel, + labelToSubroutines.get(origLabel)); + } + + return resultLabel; + } + } + + /** + * Finds a {@code Subroutine} that is returned from by a {@code ret} in + * a given block. + * + * @param label A block that originally contained a {@code ret} instruction + * @return {@code null-ok;} found subroutine or {@code null} if none + * was found + */ + private Subroutine subroutineFromRetBlock(int label) { + for (int i = subroutines.length - 1 ; i >= 0 ; i--) { + if (subroutines[i] != null) { + Subroutine subroutine = subroutines[i]; + + if (subroutine.retBlocks.get(label)) { + return subroutine; + } + } + } + + return null; + } + + + /** + * Removes all {@code move-return-address} instructions, returning a new + * {@code InsnList} if necessary. The {@code move-return-address} + * insns are dead code after subroutines have been inlined. + * + * @param insns {@code InsnList} that may contain + * {@code move-return-address} insns + * @return {@code InsnList} with {@code move-return-address} removed + */ + private InsnList filterMoveReturnAddressInsns(InsnList insns) { + int sz; + int newSz = 0; + + // First see if we need to filter, and if so what the new size will be + sz = insns.size(); + for (int i = 0; i < sz; i++) { + if (insns.get(i).getOpcode() != Rops.MOVE_RETURN_ADDRESS) { + newSz++; + } + } + + if (newSz == sz) { + return insns; + } + + // Make a new list without the MOVE_RETURN_ADDRESS insns + InsnList newInsns = new InsnList(newSz); + + int newIndex = 0; + for (int i = 0; i < sz; i++) { + Insn insn = insns.get(i); + if (insn.getOpcode() != Rops.MOVE_RETURN_ADDRESS) { + newInsns.set(newIndex++, insn); + } + } + + newInsns.setImmutable(); + return newInsns; + } + + /** + * Visits each non-subroutine block once in depth-first successor order. + * + * @param firstLabel label of start block + * @param v callback interface + */ + private void forEachNonSubBlockDepthFirst(int firstLabel, + BasicBlock.Visitor v) { + forEachNonSubBlockDepthFirst0(labelToBlock(firstLabel), + v, new BitSet(maxLabel)); + } + + /** + * Visits each block once in depth-first successor order, ignoring + * {@code jsr} targets. Worker for {@link #forEachNonSubBlockDepthFirst}. + * + * @param next next block to visit + * @param v callback interface + * @param visited set of blocks already visited + */ + private void forEachNonSubBlockDepthFirst0( + BasicBlock next, BasicBlock.Visitor v, BitSet visited) { + v.visitBlock(next); + visited.set(next.getLabel()); + + IntList successors = next.getSuccessors(); + int sz = successors.size(); + + for (int i = 0; i < sz; i++) { + int succ = successors.get(i); + + if (visited.get(succ)) { + continue; + } + + if (isSubroutineCaller(next) && i > 0) { + // ignore jsr targets + continue; + } + + /* + * Ignore missing labels: they're successors of + * subroutines that never invoke a ret. + */ + int idx = labelToResultIndex(succ); + if (idx >= 0) { + forEachNonSubBlockDepthFirst0(result.get(idx), v, visited); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/RopperMachine.java b/dexlib/src/main/java/com/android/dx/cf/code/RopperMachine.java new file mode 100644 index 000000000..67d7ab259 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/RopperMachine.java @@ -0,0 +1,1023 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.cf.iface.Method; +import com.android.dx.cf.iface.MethodList; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.code.FillArrayDataInsn; +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.InvokePolymorphicInsn; +import com.android.dx.rop.code.PlainCstInsn; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.code.SwitchInsn; +import com.android.dx.rop.code.ThrowingCstInsn; +import com.android.dx.rop.code.ThrowingInsn; +import com.android.dx.rop.code.TranslationAdvice; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.IntList; +import java.util.ArrayList; + +/** + * Machine implementation for use by {@link Ropper}. + */ +/*package*/ final class RopperMachine extends ValueAwareMachine { + /** {@code non-null;} array reflection class */ + private static final CstType ARRAY_REFLECT_TYPE = + new CstType(Type.internClassName("java/lang/reflect/Array")); + + /** + * {@code non-null;} method constant for use in converting + * {@code multianewarray} instructions + */ + private static final CstMethodRef MULTIANEWARRAY_METHOD = + new CstMethodRef(ARRAY_REFLECT_TYPE, + new CstNat(new CstString("newInstance"), + new CstString("(Ljava/lang/Class;[I)" + + "Ljava/lang/Object;"))); + + /** {@code non-null;} {@link Ropper} controlling this instance */ + private final Ropper ropper; + + /** {@code non-null;} method being converted */ + private final ConcreteMethod method; + + /** {@code non-null:} list of methods from the class whose method is being converted */ + private final MethodList methods; + + /** {@code non-null;} translation advice */ + private final TranslationAdvice advice; + + /** max locals of the method */ + private final int maxLocals; + + /** {@code non-null;} instructions for the rop basic block in-progress */ + private final ArrayList insns; + + /** {@code non-null;} catches for the block currently being processed */ + private TypeList catches; + + /** whether the catches have been used in an instruction */ + private boolean catchesUsed; + + /** whether the block contains a {@code return} */ + private boolean returns; + + /** primary successor index */ + private int primarySuccessorIndex; + + /** {@code >= 0;} number of extra basic blocks required */ + private int extraBlockCount; + + /** true if last processed block ends with a jsr or jsr_W*/ + private boolean hasJsr; + + /** true if an exception can be thrown by the last block processed */ + private boolean blockCanThrow; + + /** + * If non-null, the ReturnAddress that was used by the terminating ret + * instruction. If null, there was no ret instruction encountered. + */ + + private ReturnAddress returnAddress; + + /** + * {@code null-ok;} the appropriate {@code return} op or {@code null} + * if it is not yet known + */ + private Rop returnOp; + + /** + * {@code null-ok;} the source position for the return block or {@code null} + * if it is not yet known + */ + private SourcePosition returnPosition; + + /** + * Constructs an instance. + * + * @param ropper {@code non-null;} ropper controlling this instance + * @param method {@code non-null;} method being converted + * @param advice {@code non-null;} translation advice to use + * @param methods {@code non-null;} list of methods defined by the class + * that defines {@code method}. + */ + public RopperMachine(Ropper ropper, ConcreteMethod method, + TranslationAdvice advice, MethodList methods) { + super(method.getEffectiveDescriptor()); + + if (methods == null) { + throw new NullPointerException("methods == null"); + } + + if (ropper == null) { + throw new NullPointerException("ropper == null"); + } + + if (advice == null) { + throw new NullPointerException("advice == null"); + } + + this.ropper = ropper; + this.method = method; + this.methods = methods; + this.advice = advice; + this.maxLocals = method.getMaxLocals(); + this.insns = new ArrayList(25); + this.catches = null; + this.catchesUsed = false; + this.returns = false; + this.primarySuccessorIndex = -1; + this.extraBlockCount = 0; + this.blockCanThrow = false; + this.returnOp = null; + this.returnPosition = null; + } + + /** + * Gets the instructions array. It is shared and gets modified by + * subsequent calls to this instance. + * + * @return {@code non-null;} the instructions array + */ + public ArrayList getInsns() { + return insns; + } + + /** + * Gets the return opcode encountered, if any. + * + * @return {@code null-ok;} the return opcode + */ + public Rop getReturnOp() { + return returnOp; + } + + /** + * Gets the return position, if known. + * + * @return {@code null-ok;} the return position + */ + public SourcePosition getReturnPosition() { + return returnPosition; + } + + /** + * Gets ready to start working on a new block. This will clear the + * {@link #insns} list, set {@link #catches}, reset whether it has + * been used, reset whether the block contains a + * {@code return}, and reset {@link #primarySuccessorIndex}. + */ + public void startBlock(TypeList catches) { + this.catches = catches; + + insns.clear(); + catchesUsed = false; + returns = false; + primarySuccessorIndex = 0; + extraBlockCount = 0; + blockCanThrow = false; + hasJsr = false; + returnAddress = null; + } + + /** + * Gets whether {@link #catches} was used. This indicates that the + * last instruction in the block is one of the ones that can throw. + * + * @return whether {@code catches} has been used + */ + public boolean wereCatchesUsed() { + return catchesUsed; + } + + /** + * Gets whether the block just processed ended with a + * {@code return}. + * + * @return whether the block returns + */ + public boolean returns() { + return returns; + } + + /** + * Gets the primary successor index. This is the index into the + * successors list where the primary may be found or + * {@code -1} if there are successors but no primary + * successor. This may return something other than + * {@code -1} in the case of an instruction with no + * successors at all (primary or otherwise). + * + * @return {@code >= -1;} the primary successor index + */ + public int getPrimarySuccessorIndex() { + return primarySuccessorIndex; + } + + /** + * Gets how many extra blocks will be needed to represent the + * block currently being translated. Each extra block should consist + * of one instruction from the end of the original block. + * + * @return {@code >= 0;} the number of extra blocks needed + */ + public int getExtraBlockCount() { + return extraBlockCount; + } + + /** + * @return true if at least one of the insn processed since the last + * call to startBlock() can throw. + */ + public boolean canThrow() { + return blockCanThrow; + } + + /** + * @return true if a JSR has ben encountered since the last call to + * startBlock() + */ + public boolean hasJsr() { + return hasJsr; + } + + /** + * @return {@code true} if a {@code ret} has ben encountered since + * the last call to {@code startBlock()} + */ + public boolean hasRet() { + return returnAddress != null; + } + + /** + * @return {@code null-ok;} return address of a {@code ret} + * instruction if encountered since last call to startBlock(). + * {@code null} if no ret instruction encountered. + */ + public ReturnAddress getReturnAddress() { + return returnAddress; + } + + /** {@inheritDoc} */ + @Override + public void run(Frame frame, int offset, int opcode) { + /* + * This is the stack pointer after the opcode's arguments have been + * popped. + */ + int stackPointer = maxLocals + frame.getStack().size(); + + // The sources have to be retrieved before super.run() gets called. + RegisterSpecList sources = getSources(opcode, stackPointer); + int sourceCount = sources.size(); + + super.run(frame, offset, opcode); + + SourcePosition pos = method.makeSourcePosistion(offset); + RegisterSpec localTarget = getLocalTarget(opcode == ByteOps.ISTORE); + int destCount = resultCount(); + RegisterSpec dest; + + if (destCount == 0) { + dest = null; + switch (opcode) { + case ByteOps.POP: + case ByteOps.POP2: { + // These simply don't appear in the rop form. + return; + } + } + } else if (localTarget != null) { + dest = localTarget; + } else if (destCount == 1) { + dest = RegisterSpec.make(stackPointer, result(0)); + } else { + /* + * This clause only ever applies to the stack manipulation + * ops that have results (that is, dup* and swap but not + * pop*). + * + * What we do is first move all the source registers into + * the "temporary stack" area defined for the method, and + * then move stuff back down onto the main "stack" in the + * arrangement specified by the stack op pattern. + * + * Note: This code ends up emitting a lot of what will + * turn out to be superfluous moves (e.g., moving back and + * forth to the same local when doing a dup); however, + * that makes this code a bit easier (and goodness knows + * it doesn't need any extra complexity), and all the SSA + * stuff is going to want to deal with this sort of + * superfluous assignment anyway, so it should be a wash + * in the end. + */ + int scratchAt = ropper.getFirstTempStackReg(); + RegisterSpec[] scratchRegs = new RegisterSpec[sourceCount]; + + for (int i = 0; i < sourceCount; i++) { + RegisterSpec src = sources.get(i); + TypeBearer type = src.getTypeBearer(); + RegisterSpec scratch = src.withReg(scratchAt); + insns.add(new PlainInsn(Rops.opMove(type), pos, scratch, src)); + scratchRegs[i] = scratch; + scratchAt += src.getCategory(); + } + + for (int pattern = getAuxInt(); pattern != 0; pattern >>= 4) { + int which = (pattern & 0x0f) - 1; + RegisterSpec scratch = scratchRegs[which]; + TypeBearer type = scratch.getTypeBearer(); + insns.add(new PlainInsn(Rops.opMove(type), pos, + scratch.withReg(stackPointer), + scratch)); + stackPointer += type.getType().getCategory(); + } + return; + } + + TypeBearer destType = (dest != null) ? dest : Type.VOID; + Constant cst = getAuxCst(); + int ropOpcode; + Rop rop; + Insn insn; + + if (opcode == ByteOps.MULTIANEWARRAY) { + blockCanThrow = true; + + // Add the extra instructions for handling multianewarray. + + extraBlockCount = 6; + + /* + * Add an array constructor for the int[] containing all the + * dimensions. + */ + RegisterSpec dimsReg = + RegisterSpec.make(dest.getNextReg(), Type.INT_ARRAY); + rop = Rops.opFilledNewArray(Type.INT_ARRAY, sourceCount); + insn = new ThrowingCstInsn(rop, pos, sources, catches, + CstType.INT_ARRAY); + insns.add(insn); + + // Add a move-result for the new-filled-array + rop = Rops.opMoveResult(Type.INT_ARRAY); + insn = new PlainInsn(rop, pos, dimsReg, RegisterSpecList.EMPTY); + insns.add(insn); + + /* + * Add a const-class instruction for the specified array + * class. + */ + + /* + * Remove as many dimensions from the originally specified + * class as are given in the explicit list of dimensions, + * so as to pass the right component class to the standard + * Java library array constructor. + */ + Type componentType = ((CstType) cst).getClassType(); + for (int i = 0; i < sourceCount; i++) { + componentType = componentType.getComponentType(); + } + + RegisterSpec classReg = + RegisterSpec.make(dest.getReg(), Type.CLASS); + + if (componentType.isPrimitive()) { + /* + * The component type is primitive (e.g., int as opposed + * to Integer), so we have to fetch the corresponding + * TYPE class. + */ + CstFieldRef typeField = + CstFieldRef.forPrimitiveType(componentType); + insn = new ThrowingCstInsn(Rops.GET_STATIC_OBJECT, pos, + RegisterSpecList.EMPTY, + catches, typeField); + } else { + /* + * The component type is an object type, so just make a + * normal class reference. + */ + insn = new ThrowingCstInsn(Rops.CONST_OBJECT, pos, + RegisterSpecList.EMPTY, catches, + new CstType(componentType)); + } + + insns.add(insn); + + // Add a move-result-pseudo for the get-static or const + rop = Rops.opMoveResultPseudo(classReg.getType()); + insn = new PlainInsn(rop, pos, classReg, RegisterSpecList.EMPTY); + insns.add(insn); + + /* + * Add a call to the "multianewarray method," that is, + * Array.newInstance(class, dims). Note: The result type + * of newInstance() is Object, which is why the last + * instruction in this sequence is a cast to the right + * type for the original instruction. + */ + + RegisterSpec objectReg = + RegisterSpec.make(dest.getReg(), Type.OBJECT); + + insn = new ThrowingCstInsn( + Rops.opInvokeStatic(MULTIANEWARRAY_METHOD.getPrototype()), + pos, RegisterSpecList.make(classReg, dimsReg), + catches, MULTIANEWARRAY_METHOD); + insns.add(insn); + + // Add a move-result. + rop = Rops.opMoveResult(MULTIANEWARRAY_METHOD.getPrototype() + .getReturnType()); + insn = new PlainInsn(rop, pos, objectReg, RegisterSpecList.EMPTY); + insns.add(insn); + + /* + * And finally, set up for the remainder of this method to + * add an appropriate cast. + */ + + opcode = ByteOps.CHECKCAST; + sources = RegisterSpecList.make(objectReg); + } else if (opcode == ByteOps.JSR) { + // JSR has no Rop instruction + hasJsr = true; + return; + } else if (opcode == ByteOps.RET) { + try { + returnAddress = (ReturnAddress)arg(0); + } catch (ClassCastException ex) { + throw new RuntimeException( + "Argument to RET was not a ReturnAddress", ex); + } + // RET has no Rop instruction. + return; + } + + ropOpcode = jopToRopOpcode(opcode, cst); + rop = Rops.ropFor(ropOpcode, destType, sources, cst); + + Insn moveResult = null; + if (dest != null && rop.isCallLike()) { + /* + * We're going to want to have a move-result in the next + * basic block. + */ + extraBlockCount++; + + moveResult = new PlainInsn( + Rops.opMoveResult(((CstMethodRef) cst).getPrototype() + .getReturnType()), pos, dest, RegisterSpecList.EMPTY); + + dest = null; + } else if (dest != null && rop.canThrow()) { + /* + * We're going to want to have a move-result-pseudo in the + * next basic block. + */ + extraBlockCount++; + + moveResult = new PlainInsn( + Rops.opMoveResultPseudo(dest.getTypeBearer()), + pos, dest, RegisterSpecList.EMPTY); + + dest = null; + } + if (ropOpcode == RegOps.NEW_ARRAY) { + /* + * In the original bytecode, this was either a primitive + * array constructor "newarray" or an object array + * constructor "anewarray". In the former case, there is + * no explicit constant, and in the latter, the constant + * is for the element type and not the array type. The rop + * instruction form for both of these is supposed to be + * the resulting array type, so we initialize / alter + * "cst" here, accordingly. Conveniently enough, the rop + * opcode already gets constructed with the proper array + * type. + */ + cst = CstType.intern(rop.getResult()); + } else if ((cst == null) && (sourceCount == 2)) { + TypeBearer firstType = sources.get(0).getTypeBearer(); + TypeBearer lastType = sources.get(1).getTypeBearer(); + + if ((lastType.isConstant() || firstType.isConstant()) && + advice.hasConstantOperation(rop, sources.get(0), + sources.get(1))) { + + if (lastType.isConstant()) { + /* + * The target architecture has an instruction that can + * build in the constant found in the second argument, + * so pull it out of the sources and just use it as a + * constant here. + */ + cst = (Constant) lastType; + sources = sources.withoutLast(); + + // For subtraction, change to addition and invert constant + if (rop.getOpcode() == RegOps.SUB) { + ropOpcode = RegOps.ADD; + CstInteger cstInt = (CstInteger) lastType; + cst = CstInteger.make(-cstInt.getValue()); + } + } else { + /* + * The target architecture has an instruction that can + * build in the constant found in the first argument, + * so pull it out of the sources and just use it as a + * constant here. + */ + cst = (Constant) firstType; + sources = sources.withoutFirst(); + } + + rop = Rops.ropFor(ropOpcode, destType, sources, cst); + } + } + + SwitchList cases = getAuxCases(); + ArrayList initValues = getInitValues(); + boolean canThrow = rop.canThrow(); + + blockCanThrow |= canThrow; + + if (cases != null) { + if (cases.size() == 0) { + // It's a default-only switch statement. It can happen! + insn = new PlainInsn(Rops.GOTO, pos, null, + RegisterSpecList.EMPTY); + primarySuccessorIndex = 0; + } else { + IntList values = cases.getValues(); + insn = new SwitchInsn(rop, pos, dest, sources, values); + primarySuccessorIndex = values.size(); + } + } else if (ropOpcode == RegOps.RETURN) { + /* + * Returns get turned into the combination of a move (if + * non-void and if the return doesn't already mention + * register 0) and a goto (to the return block). + */ + if (sources.size() != 0) { + RegisterSpec source = sources.get(0); + TypeBearer type = source.getTypeBearer(); + if (source.getReg() != 0) { + insns.add(new PlainInsn(Rops.opMove(type), pos, + RegisterSpec.make(0, type), + source)); + } + } + insn = new PlainInsn(Rops.GOTO, pos, null, RegisterSpecList.EMPTY); + primarySuccessorIndex = 0; + updateReturnOp(rop, pos); + returns = true; + } else if (cst != null) { + if (canThrow) { + if (rop.getOpcode() == RegOps.INVOKE_POLYMORPHIC) { + insn = makeInvokePolymorphicInsn(rop, pos, sources, catches, cst); + } else { + insn = new ThrowingCstInsn(rop, pos, sources, catches, cst); + } + catchesUsed = true; + primarySuccessorIndex = catches.size(); + } else { + insn = new PlainCstInsn(rop, pos, dest, sources, cst); + } + } else if (canThrow) { + insn = new ThrowingInsn(rop, pos, sources, catches); + catchesUsed = true; + if (opcode == ByteOps.ATHROW) { + /* + * The op athrow is the only one where it's possible + * to have non-empty successors and yet not have a + * primary successor. + */ + primarySuccessorIndex = -1; + } else { + primarySuccessorIndex = catches.size(); + } + } else { + insn = new PlainInsn(rop, pos, dest, sources); + } + + insns.add(insn); + + if (moveResult != null) { + insns.add(moveResult); + } + + /* + * If initValues is non-null, it means that the parser has + * seen a group of compatible constant initialization + * bytecodes that are applied to the current newarray. The + * action we take here is to convert these initialization + * bytecodes into a single fill-array-data ROP which lays out + * all the constant values in a table. + */ + if (initValues != null) { + extraBlockCount++; + insn = new FillArrayDataInsn(Rops.FILL_ARRAY_DATA, pos, + RegisterSpecList.make(moveResult.getResult()), initValues, + cst); + insns.add(insn); + } + } + + /** + * Helper for {@link #run}, which gets the list of sources for the. + * instruction. + * + * @param opcode the opcode being translated + * @param stackPointer {@code >= 0;} the stack pointer after the + * instruction's arguments have been popped + * @return {@code non-null;} the sources + */ + private RegisterSpecList getSources(int opcode, int stackPointer) { + int count = argCount(); + + if (count == 0) { + // We get an easy out if there aren't any sources. + return RegisterSpecList.EMPTY; + } + + int localIndex = getLocalIndex(); + RegisterSpecList sources; + + if (localIndex >= 0) { + // The instruction is operating on a local variable. + sources = new RegisterSpecList(1); + sources.set(0, RegisterSpec.make(localIndex, arg(0))); + } else { + sources = new RegisterSpecList(count); + int regAt = stackPointer; + for (int i = 0; i < count; i++) { + RegisterSpec spec = RegisterSpec.make(regAt, arg(i)); + sources.set(i, spec); + regAt += spec.getCategory(); + } + + switch (opcode) { + case ByteOps.IASTORE: { + /* + * The Java argument order for array stores is + * (array, index, value), but the rop argument + * order is (value, array, index). The following + * code gets the right arguments in the right + * places. + */ + if (count != 3) { + throw new RuntimeException("shouldn't happen"); + } + RegisterSpec array = sources.get(0); + RegisterSpec index = sources.get(1); + RegisterSpec value = sources.get(2); + sources.set(0, value); + sources.set(1, array); + sources.set(2, index); + break; + } + case ByteOps.PUTFIELD: { + /* + * Similar to above: The Java argument order for + * putfield is (object, value), but the rop + * argument order is (value, object). + */ + if (count != 2) { + throw new RuntimeException("shouldn't happen"); + } + RegisterSpec obj = sources.get(0); + RegisterSpec value = sources.get(1); + sources.set(0, value); + sources.set(1, obj); + break; + } + } + } + + sources.setImmutable(); + return sources; + } + + /** + * Sets or updates the information about the return block. + * + * @param op {@code non-null;} the opcode to use + * @param pos {@code non-null;} the position to use + */ + private void updateReturnOp(Rop op, SourcePosition pos) { + if (op == null) { + throw new NullPointerException("op == null"); + } + + if (pos == null) { + throw new NullPointerException("pos == null"); + } + + if (returnOp == null) { + returnOp = op; + returnPosition = pos; + } else { + if (returnOp != op) { + throw new SimException("return op mismatch: " + op + ", " + + returnOp); + } + + if (pos.getLine() > returnPosition.getLine()) { + // Pick the largest line number to be the "canonical" return. + returnPosition = pos; + } + } + } + + /** + * Gets the register opcode for the given Java opcode. + * + * @param jop {@code jop >= 0;} the Java opcode + * @param cst {@code null-ok;} the constant argument, if any + * @return {@code >= 0;} the corresponding register opcode + */ + private int jopToRopOpcode(int jop, Constant cst) { + switch (jop) { + case ByteOps.POP: + case ByteOps.POP2: + case ByteOps.DUP: + case ByteOps.DUP_X1: + case ByteOps.DUP_X2: + case ByteOps.DUP2: + case ByteOps.DUP2_X1: + case ByteOps.DUP2_X2: + case ByteOps.SWAP: + case ByteOps.JSR: + case ByteOps.RET: + case ByteOps.MULTIANEWARRAY: { + // These need to be taken care of specially. + break; + } + case ByteOps.NOP: { + return RegOps.NOP; + } + case ByteOps.LDC: + case ByteOps.LDC2_W: { + return RegOps.CONST; + } + case ByteOps.ILOAD: + case ByteOps.ISTORE: { + return RegOps.MOVE; + } + case ByteOps.IALOAD: { + return RegOps.AGET; + } + case ByteOps.IASTORE: { + return RegOps.APUT; + } + case ByteOps.IADD: + case ByteOps.IINC: { + return RegOps.ADD; + } + case ByteOps.ISUB: { + return RegOps.SUB; + } + case ByteOps.IMUL: { + return RegOps.MUL; + } + case ByteOps.IDIV: { + return RegOps.DIV; + } + case ByteOps.IREM: { + return RegOps.REM; + } + case ByteOps.INEG: { + return RegOps.NEG; + } + case ByteOps.ISHL: { + return RegOps.SHL; + } + case ByteOps.ISHR: { + return RegOps.SHR; + } + case ByteOps.IUSHR: { + return RegOps.USHR; + } + case ByteOps.IAND: { + return RegOps.AND; + } + case ByteOps.IOR: { + return RegOps.OR; + } + case ByteOps.IXOR: { + return RegOps.XOR; + } + case ByteOps.I2L: + case ByteOps.I2F: + case ByteOps.I2D: + case ByteOps.L2I: + case ByteOps.L2F: + case ByteOps.L2D: + case ByteOps.F2I: + case ByteOps.F2L: + case ByteOps.F2D: + case ByteOps.D2I: + case ByteOps.D2L: + case ByteOps.D2F: { + return RegOps.CONV; + } + case ByteOps.I2B: { + return RegOps.TO_BYTE; + } + case ByteOps.I2C: { + return RegOps.TO_CHAR; + } + case ByteOps.I2S: { + return RegOps.TO_SHORT; + } + case ByteOps.LCMP: + case ByteOps.FCMPL: + case ByteOps.DCMPL: { + return RegOps.CMPL; + } + case ByteOps.FCMPG: + case ByteOps.DCMPG: { + return RegOps.CMPG; + } + case ByteOps.IFEQ: + case ByteOps.IF_ICMPEQ: + case ByteOps.IF_ACMPEQ: + case ByteOps.IFNULL: { + return RegOps.IF_EQ; + } + case ByteOps.IFNE: + case ByteOps.IF_ICMPNE: + case ByteOps.IF_ACMPNE: + case ByteOps.IFNONNULL: { + return RegOps.IF_NE; + } + case ByteOps.IFLT: + case ByteOps.IF_ICMPLT: { + return RegOps.IF_LT; + } + case ByteOps.IFGE: + case ByteOps.IF_ICMPGE: { + return RegOps.IF_GE; + } + case ByteOps.IFGT: + case ByteOps.IF_ICMPGT: { + return RegOps.IF_GT; + } + case ByteOps.IFLE: + case ByteOps.IF_ICMPLE: { + return RegOps.IF_LE; + } + case ByteOps.GOTO: { + return RegOps.GOTO; + } + case ByteOps.LOOKUPSWITCH: { + return RegOps.SWITCH; + } + case ByteOps.IRETURN: + case ByteOps.RETURN: { + return RegOps.RETURN; + } + case ByteOps.GETSTATIC: { + return RegOps.GET_STATIC; + } + case ByteOps.PUTSTATIC: { + return RegOps.PUT_STATIC; + } + case ByteOps.GETFIELD: { + return RegOps.GET_FIELD; + } + case ByteOps.PUTFIELD: { + return RegOps.PUT_FIELD; + } + case ByteOps.INVOKEVIRTUAL: { + CstMethodRef ref = (CstMethodRef) cst; + // The java bytecode specification does not explicitly disallow + // invokevirtual calls to any instance method, though it + // specifies that instance methods and private methods "should" be + // called using "invokespecial" instead of "invokevirtual". + // Several bytecode tools generate "invokevirtual" instructions for + // invocation of private methods. + // + // The dalvik opcode specification on the other hand allows + // invoke-virtual to be used only with "normal" virtual methods, + // i.e, ones that are not private, static, final or constructors. + // We therefore need to transform invoke-virtual calls to private + // instance methods to invoke-direct opcodes. + // + // Note that it assumes that all methods for a given class are + // defined in the same dex file. + // + // NOTE: This is a slow O(n) loop, and can be replaced with a + // faster implementation (at the cost of higher memory usage) + // if it proves to be a hot area of code. + if (ref.getDefiningClass().equals(method.getDefiningClass())) { + for (int i = 0; i < methods.size(); ++i) { + final Method m = methods.get(i); + if (AccessFlags.isPrivate(m.getAccessFlags()) && + ref.getNat().equals(m.getNat())) { + return RegOps.INVOKE_DIRECT; + } + } + } + // If the method reference is a signature polymorphic method + // substitute invoke-polymorphic for invoke-virtual. This only + // affects MethodHandle.invoke and MethodHandle.invokeExact. + if (ref.isSignaturePolymorphic()) { + return RegOps.INVOKE_POLYMORPHIC; + } + return RegOps.INVOKE_VIRTUAL; + } + case ByteOps.INVOKESPECIAL: { + /* + * Determine whether the opcode should be + * INVOKE_DIRECT or INVOKE_SUPER. See vmspec-2 section 6 + * on "invokespecial" as well as section 4.8.2 (7th + * bullet point) for the gory details. + */ + /* TODO: Consider checking that invoke-special target + * method is private, or constructor since otherwise ART + * verifier will reject it. + */ + CstMethodRef ref = (CstMethodRef) cst; + if (ref.isInstanceInit() || + (ref.getDefiningClass().equals(method.getDefiningClass()))) { + return RegOps.INVOKE_DIRECT; + } + return RegOps.INVOKE_SUPER; + } + case ByteOps.INVOKESTATIC: { + return RegOps.INVOKE_STATIC; + } + case ByteOps.INVOKEINTERFACE: { + return RegOps.INVOKE_INTERFACE; + } + case ByteOps.NEW: { + return RegOps.NEW_INSTANCE; + } + case ByteOps.NEWARRAY: + case ByteOps.ANEWARRAY: { + return RegOps.NEW_ARRAY; + } + case ByteOps.ARRAYLENGTH: { + return RegOps.ARRAY_LENGTH; + } + case ByteOps.ATHROW: { + return RegOps.THROW; + } + case ByteOps.CHECKCAST: { + return RegOps.CHECK_CAST; + } + case ByteOps.INSTANCEOF: { + return RegOps.INSTANCE_OF; + } + case ByteOps.MONITORENTER: { + return RegOps.MONITOR_ENTER; + } + case ByteOps.MONITOREXIT: { + return RegOps.MONITOR_EXIT; + } + } + + throw new RuntimeException("shouldn't happen"); + } + + private Insn makeInvokePolymorphicInsn(Rop rop, SourcePosition pos, RegisterSpecList sources, + TypeList catches, Constant cst) { + CstMethodRef cstMethodRef = (CstMethodRef) cst; + return new InvokePolymorphicInsn(rop, pos, sources, catches, cstMethodRef); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/SimException.java b/dexlib/src/main/java/com/android/dx/cf/code/SimException.java new file mode 100644 index 000000000..7cbab04c7 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/SimException.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dex.util.ExceptionWithContext; + +/** + * Exception from simulation. + */ +public class SimException + extends ExceptionWithContext { + public SimException(String message) { + super(message); + } + + public SimException(Throwable cause) { + super(cause); + } + + public SimException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/Simulator.java b/dexlib/src/main/java/com/android/dx/cf/code/Simulator.java new file mode 100644 index 000000000..5b7708278 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/Simulator.java @@ -0,0 +1,809 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dex.DexFormat; +import com.android.dx.dex.DexOptions; +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstInterfaceMethodRef; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.Type; +import com.android.dx.util.Hex; +import java.util.ArrayList; + +/** + * Class which knows how to simulate the effects of executing bytecode. + * + *

Note: This class is not thread-safe. If multiple threads + * need to use a single instance, they must synchronize access explicitly + * between themselves.

+ */ +public class Simulator { + /** + * {@code non-null;} canned error message for local variable + * table mismatches + */ + private static final String LOCAL_MISMATCH_ERROR = + "This is symptomatic of .class transformation tools that ignore " + + "local variable information."; + + /** {@code non-null;} machine to use when simulating */ + private final Machine machine; + + /** {@code non-null;} array of bytecode */ + private final BytecodeArray code; + + /** {@code non-null;} local variable information */ + private final LocalVariableList localVariables; + + /** {@code non-null;} visitor instance to use */ + private final SimVisitor visitor; + + /** {@code non-null;} options for dex output */ + private final DexOptions dexOptions; + + /** + * Constructs an instance. + * + * @param machine {@code non-null;} machine to use when simulating + * @param method {@code non-null;} method data to use + * @param dexOptions {@code non-null;} options for dex output + */ + public Simulator(Machine machine, ConcreteMethod method, DexOptions dexOptions) { + if (machine == null) { + throw new NullPointerException("machine == null"); + } + + if (method == null) { + throw new NullPointerException("method == null"); + } + + this.machine = machine; + this.code = method.getCode(); + this.localVariables = method.getLocalVariables(); + this.visitor = new SimVisitor(); + this.dexOptions = dexOptions; + } + + /** + * Simulates the effect of executing the given basic block. This modifies + * the passed-in frame to represent the end result. + * + * @param bb {@code non-null;} the basic block + * @param frame {@code non-null;} frame to operate on + */ + public void simulate(ByteBlock bb, Frame frame) { + int end = bb.getEnd(); + + visitor.setFrame(frame); + + try { + for (int off = bb.getStart(); off < end; /*off*/) { + int length = code.parseInstruction(off, visitor); + visitor.setPreviousOffset(off); + off += length; + } + } catch (SimException ex) { + frame.annotate(ex); + throw ex; + } + } + + /** + * Simulates the effect of the instruction at the given offset, by + * making appropriate calls on the given frame. + * + * @param offset {@code offset >= 0;} offset of the instruction to simulate + * @param frame {@code non-null;} frame to operate on + * @return the length of the instruction, in bytes + */ + public int simulate(int offset, Frame frame) { + visitor.setFrame(frame); + return code.parseInstruction(offset, visitor); + } + + /** + * Constructs an "illegal top-of-stack" exception, for the stack + * manipulation opcodes. + */ + private static SimException illegalTos() { + return new SimException("stack mismatch: illegal " + + "top-of-stack for opcode"); + } + + /** + * Returns the required array type for an array load or store + * instruction, based on a given implied type and an observed + * actual array type. + * + *

The interesting cases here have to do with object arrays, + * byte[]s, boolean[]s, and + * known-nulls.

+ * + *

In the case of arrays of objects, we want to narrow the type + * to the actual array present on the stack, as long as what is + * present is an object type. Similarly, due to a quirk of the + * original bytecode representation, the instructions for dealing + * with byte[] and boolean[] are + * undifferentiated, and we aim here to return whichever one was + * actually present on the stack.

+ * + *

In the case where there is a known-null on the stack where + * an array is expected, our behavior depends on the implied type + * of the instruction. When the implied type is a reference, we + * don't attempt to infer anything, as we don't know the dimension + * of the null constant and thus any explicit inferred type could + * be wrong. When the implied type is a primitive, we fall back to + * the implied type of the instruction. Due to the quirk described + * above, this means that source code that uses + * boolean[] might get translated surprisingly -- but + * correctly -- into an instruction that specifies a + * byte[]. It will be correct, because should the + * code actually execute, it will necessarily throw a + * NullPointerException, and it won't matter what + * opcode variant is used to achieve that result.

+ * + * @param impliedType {@code non-null;} type implied by the + * instruction; is not an array type + * @param foundArrayType {@code non-null;} type found on the + * stack; is either an array type or a known-null + * @return {@code non-null;} the array type that should be + * required in this context + */ + private static Type requiredArrayTypeFor(Type impliedType, + Type foundArrayType) { + if (foundArrayType == Type.KNOWN_NULL) { + return impliedType.isReference() + ? Type.KNOWN_NULL + : impliedType.getArrayType(); + } + + if ((impliedType == Type.OBJECT) + && foundArrayType.isArray() + && foundArrayType.getComponentType().isReference()) { + return foundArrayType; + } + + if ((impliedType == Type.BYTE) + && (foundArrayType == Type.BOOLEAN_ARRAY)) { + /* + * Per above, an instruction with implied byte[] is also + * allowed to be used on boolean[]. + */ + return Type.BOOLEAN_ARRAY; + } + + return impliedType.getArrayType(); + } + + /** + * Bytecode visitor used during simulation. + */ + private class SimVisitor implements BytecodeArray.Visitor { + /** + * {@code non-null;} machine instance to use (just to avoid excessive + * cross-object field access) + */ + private final Machine machine; + + /** + * {@code null-ok;} frame to use; set with each call to + * {@link Simulator#simulate} + */ + private Frame frame; + + /** offset of the previous bytecode */ + private int previousOffset; + + /** + * Constructs an instance. + */ + public SimVisitor() { + this.machine = Simulator.this.machine; + this.frame = null; + } + + /** + * Sets the frame to act on. + * + * @param frame {@code non-null;} the frame + */ + public void setFrame(Frame frame) { + if (frame == null) { + throw new NullPointerException("frame == null"); + } + + this.frame = frame; + } + + /** {@inheritDoc} */ + public void visitInvalid(int opcode, int offset, int length) { + throw new SimException("invalid opcode " + Hex.u1(opcode)); + } + + /** {@inheritDoc} */ + public void visitNoArgs(int opcode, int offset, int length, + Type type) { + switch (opcode) { + case ByteOps.NOP: { + machine.clearArgs(); + break; + } + case ByteOps.INEG: { + machine.popArgs(frame, type); + break; + } + case ByteOps.I2L: + case ByteOps.I2F: + case ByteOps.I2D: + case ByteOps.I2B: + case ByteOps.I2C: + case ByteOps.I2S: { + machine.popArgs(frame, Type.INT); + break; + } + case ByteOps.L2I: + case ByteOps.L2F: + case ByteOps.L2D: { + machine.popArgs(frame, Type.LONG); + break; + } + case ByteOps.F2I: + case ByteOps.F2L: + case ByteOps.F2D: { + machine.popArgs(frame, Type.FLOAT); + break; + } + case ByteOps.D2I: + case ByteOps.D2L: + case ByteOps.D2F: { + machine.popArgs(frame, Type.DOUBLE); + break; + } + case ByteOps.RETURN: { + machine.clearArgs(); + checkReturnType(Type.VOID); + break; + } + case ByteOps.IRETURN: { + Type checkType = type; + if (type == Type.OBJECT) { + /* + * For an object return, use the best-known + * type of the popped value. + */ + checkType = frame.getStack().peekType(0); + } + machine.popArgs(frame, type); + checkReturnType(checkType); + break; + } + case ByteOps.POP: { + Type peekType = frame.getStack().peekType(0); + if (peekType.isCategory2()) { + throw illegalTos(); + } + machine.popArgs(frame, 1); + break; + } + case ByteOps.ARRAYLENGTH: { + Type arrayType = frame.getStack().peekType(0); + if (!arrayType.isArrayOrKnownNull()) { + throw new SimException("type mismatch: expected " + + "array type but encountered " + + arrayType.toHuman()); + } + machine.popArgs(frame, Type.OBJECT); + break; + } + case ByteOps.ATHROW: + case ByteOps.MONITORENTER: + case ByteOps.MONITOREXIT: { + machine.popArgs(frame, Type.OBJECT); + break; + } + case ByteOps.IALOAD: { + /* + * See comment on requiredArrayTypeFor() for explanation + * about what's going on here. + */ + Type foundArrayType = frame.getStack().peekType(1); + Type requiredArrayType = + requiredArrayTypeFor(type, foundArrayType); + + // Make type agree with the discovered requiredArrayType. + type = (requiredArrayType == Type.KNOWN_NULL) + ? Type.KNOWN_NULL + : requiredArrayType.getComponentType(); + + machine.popArgs(frame, requiredArrayType, Type.INT); + break; + } + case ByteOps.IADD: + case ByteOps.ISUB: + case ByteOps.IMUL: + case ByteOps.IDIV: + case ByteOps.IREM: + case ByteOps.IAND: + case ByteOps.IOR: + case ByteOps.IXOR: { + machine.popArgs(frame, type, type); + break; + } + case ByteOps.ISHL: + case ByteOps.ISHR: + case ByteOps.IUSHR: { + machine.popArgs(frame, type, Type.INT); + break; + } + case ByteOps.LCMP: { + machine.popArgs(frame, Type.LONG, Type.LONG); + break; + } + case ByteOps.FCMPL: + case ByteOps.FCMPG: { + machine.popArgs(frame, Type.FLOAT, Type.FLOAT); + break; + } + case ByteOps.DCMPL: + case ByteOps.DCMPG: { + machine.popArgs(frame, Type.DOUBLE, Type.DOUBLE); + break; + } + case ByteOps.IASTORE: { + /* + * See comment on requiredArrayTypeFor() for + * explanation about what's going on here. In + * addition to that, the category 1 vs. 2 thing + * below is to deal with the fact that, if the + * element type is category 2, we have to skip + * over one extra stack slot to find the array. + */ + ExecutionStack stack = frame.getStack(); + int peekDepth = type.isCategory1() ? 2 : 3; + Type foundArrayType = stack.peekType(peekDepth); + boolean foundArrayLocal = stack.peekLocal(peekDepth); + + Type requiredArrayType = + requiredArrayTypeFor(type, foundArrayType); + + /* + * Make type agree with the discovered requiredArrayType + * if it has local info. + */ + if (foundArrayLocal) { + type = (requiredArrayType == Type.KNOWN_NULL) + ? Type.KNOWN_NULL + : requiredArrayType.getComponentType(); + } + + machine.popArgs(frame, requiredArrayType, Type.INT, type); + break; + } + case ByteOps.POP2: + case ByteOps.DUP2: { + ExecutionStack stack = frame.getStack(); + int pattern; + + if (stack.peekType(0).isCategory2()) { + // "form 2" in vmspec-2 + machine.popArgs(frame, 1); + pattern = 0x11; + } else if (stack.peekType(1).isCategory1()) { + // "form 1" + machine.popArgs(frame, 2); + pattern = 0x2121; + } else { + throw illegalTos(); + } + + if (opcode == ByteOps.DUP2) { + machine.auxIntArg(pattern); + } + break; + } + case ByteOps.DUP: { + Type peekType = frame.getStack().peekType(0); + + if (peekType.isCategory2()) { + throw illegalTos(); + } + + machine.popArgs(frame, 1); + machine.auxIntArg(0x11); + break; + } + case ByteOps.DUP_X1: { + ExecutionStack stack = frame.getStack(); + + if (!(stack.peekType(0).isCategory1() && + stack.peekType(1).isCategory1())) { + throw illegalTos(); + } + + machine.popArgs(frame, 2); + machine.auxIntArg(0x212); + break; + } + case ByteOps.DUP_X2: { + ExecutionStack stack = frame.getStack(); + + if (stack.peekType(0).isCategory2()) { + throw illegalTos(); + } + + if (stack.peekType(1).isCategory2()) { + // "form 2" in vmspec-2 + machine.popArgs(frame, 2); + machine.auxIntArg(0x212); + } else if (stack.peekType(2).isCategory1()) { + // "form 1" + machine.popArgs(frame, 3); + machine.auxIntArg(0x3213); + } else { + throw illegalTos(); + } + break; + } + case ByteOps.DUP2_X1: { + ExecutionStack stack = frame.getStack(); + + if (stack.peekType(0).isCategory2()) { + // "form 2" in vmspec-2 + if (stack.peekType(2).isCategory2()) { + throw illegalTos(); + } + machine.popArgs(frame, 2); + machine.auxIntArg(0x212); + } else { + // "form 1" + if (stack.peekType(1).isCategory2() || + stack.peekType(2).isCategory2()) { + throw illegalTos(); + } + machine.popArgs(frame, 3); + machine.auxIntArg(0x32132); + } + break; + } + case ByteOps.DUP2_X2: { + ExecutionStack stack = frame.getStack(); + + if (stack.peekType(0).isCategory2()) { + if (stack.peekType(2).isCategory2()) { + // "form 4" in vmspec-2 + machine.popArgs(frame, 2); + machine.auxIntArg(0x212); + } else if (stack.peekType(3).isCategory1()) { + // "form 2" + machine.popArgs(frame, 3); + machine.auxIntArg(0x3213); + } else { + throw illegalTos(); + } + } else if (stack.peekType(1).isCategory1()) { + if (stack.peekType(2).isCategory2()) { + // "form 3" + machine.popArgs(frame, 3); + machine.auxIntArg(0x32132); + } else if (stack.peekType(3).isCategory1()) { + // "form 1" + machine.popArgs(frame, 4); + machine.auxIntArg(0x432143); + } else { + throw illegalTos(); + } + } else { + throw illegalTos(); + } + break; + } + case ByteOps.SWAP: { + ExecutionStack stack = frame.getStack(); + + if (!(stack.peekType(0).isCategory1() && + stack.peekType(1).isCategory1())) { + throw illegalTos(); + } + + machine.popArgs(frame, 2); + machine.auxIntArg(0x12); + break; + } + default: { + visitInvalid(opcode, offset, length); + return; + } + } + + machine.auxType(type); + machine.run(frame, offset, opcode); + } + + /** + * Checks whether the prototype is compatible with returning the + * given type, and throws if not. + * + * @param encountered {@code non-null;} the encountered return type + */ + private void checkReturnType(Type encountered) { + Type returnType = machine.getPrototype().getReturnType(); + + /* + * Check to see if the prototype's return type is + * possibly assignable from the type we encountered. This + * takes care of all the salient cases (types are the same, + * they're compatible primitive types, etc.). + */ + if (!Merger.isPossiblyAssignableFrom(returnType, encountered)) { + throw new SimException("return type mismatch: prototype " + + "indicates " + returnType.toHuman() + + ", but encountered type " + encountered.toHuman()); + } + } + + /** {@inheritDoc} */ + public void visitLocal(int opcode, int offset, int length, + int idx, Type type, int value) { + /* + * Note that the "type" parameter is always the simplest + * type based on the original opcode, e.g., "int" for + * "iload" (per se) and "Object" for "aload". So, when + * possible, we replace the type with the one indicated in + * the local variable table, though we still need to check + * to make sure it's valid for the opcode. + * + * The reason we use (offset + length) for the localOffset + * for a store is because it is only after the store that + * the local type becomes valid. On the other hand, the + * type associated with a load is valid at the start of + * the instruction. + */ + int localOffset = + (opcode == ByteOps.ISTORE) ? (offset + length) : offset; + LocalVariableList.Item local = + localVariables.pcAndIndexToLocal(localOffset, idx); + Type localType; + + if (local != null) { + localType = local.getType(); + if (localType.getBasicFrameType() != + type.getBasicFrameType()) { + // wrong type, ignore local variable info + local = null; + localType = type; + } + } else { + localType = type; + } + + switch (opcode) { + case ByteOps.ILOAD: + case ByteOps.RET: { + machine.localArg(frame, idx); + machine.localInfo(local != null); + machine.auxType(type); + break; + } + case ByteOps.ISTORE: { + LocalItem item + = (local == null) ? null : local.getLocalItem(); + machine.popArgs(frame, type); + machine.auxType(type); + machine.localTarget(idx, localType, item); + break; + } + case ByteOps.IINC: { + LocalItem item + = (local == null) ? null : local.getLocalItem(); + machine.localArg(frame, idx); + machine.localTarget(idx, localType, item); + machine.auxType(type); + machine.auxIntArg(value); + machine.auxCstArg(CstInteger.make(value)); + break; + } + default: { + visitInvalid(opcode, offset, length); + return; + } + } + + machine.run(frame, offset, opcode); + } + + /** {@inheritDoc} */ + public void visitConstant(int opcode, int offset, int length, + Constant cst, int value) { + switch (opcode) { + case ByteOps.ANEWARRAY: { + machine.popArgs(frame, Type.INT); + break; + } + case ByteOps.PUTSTATIC: { + Type fieldType = ((CstFieldRef) cst).getType(); + machine.popArgs(frame, fieldType); + break; + } + case ByteOps.GETFIELD: + case ByteOps.CHECKCAST: + case ByteOps.INSTANCEOF: { + machine.popArgs(frame, Type.OBJECT); + break; + } + case ByteOps.PUTFIELD: { + Type fieldType = ((CstFieldRef) cst).getType(); + machine.popArgs(frame, Type.OBJECT, fieldType); + break; + } + case ByteOps.INVOKEINTERFACE: + case ByteOps.INVOKEVIRTUAL: + case ByteOps.INVOKESPECIAL: + case ByteOps.INVOKESTATIC: { + /* + * Convert the interface method ref into a normal + * method ref if necessary. + */ + if (cst instanceof CstInterfaceMethodRef) { + if (opcode != ByteOps.INVOKEINTERFACE) { + if (!dexOptions.canUseDefaultInterfaceMethods()) { + throw new SimException( + "default or static interface method used without " + + "--min-sdk-version >= " + DexFormat.API_DEFAULT_INTERFACE_METHODS); + } + } + cst = ((CstInterfaceMethodRef) cst).toMethodRef(); + } + + /* + * Check whether invoke-polymorphic is required and supported. + */ + if (cst instanceof CstMethodRef) { + CstMethodRef methodRef = (CstMethodRef) cst; + if (methodRef.isSignaturePolymorphic()) { + if (!dexOptions.canUseInvokePolymorphic()) { + throw new SimException( + "signature-polymorphic method called without " + + "--min-sdk-version >= " + DexFormat.API_INVOKE_POLYMORPHIC); + } + if (opcode != ByteOps.INVOKEVIRTUAL) { + throw new SimException( + "Unsupported signature polymorphic invocation (" + + ByteOps.opName(opcode) + ")"); + } + } + } + + /* + * Get the instance or static prototype, and use it to + * direct the machine. + */ + boolean staticMethod = (opcode == ByteOps.INVOKESTATIC); + Prototype prototype = + ((CstMethodRef) cst).getPrototype(staticMethod); + machine.popArgs(frame, prototype); + break; + } + case ByteOps.MULTIANEWARRAY: { + /* + * The "value" here is the count of dimensions to + * create. Make a prototype of that many "int" + * types, and tell the machine to pop them. This + * isn't the most efficient way in the world to do + * this, but then again, multianewarray is pretty + * darn rare and so not worth much effort + * optimizing for. + */ + Prototype prototype = + Prototype.internInts(Type.VOID, value); + machine.popArgs(frame, prototype); + break; + } + default: { + machine.clearArgs(); + break; + } + } + + machine.auxIntArg(value); + machine.auxCstArg(cst); + machine.run(frame, offset, opcode); + } + + /** {@inheritDoc} */ + public void visitBranch(int opcode, int offset, int length, + int target) { + switch (opcode) { + case ByteOps.IFEQ: + case ByteOps.IFNE: + case ByteOps.IFLT: + case ByteOps.IFGE: + case ByteOps.IFGT: + case ByteOps.IFLE: { + machine.popArgs(frame, Type.INT); + break; + } + case ByteOps.IFNULL: + case ByteOps.IFNONNULL: { + machine.popArgs(frame, Type.OBJECT); + break; + } + case ByteOps.IF_ICMPEQ: + case ByteOps.IF_ICMPNE: + case ByteOps.IF_ICMPLT: + case ByteOps.IF_ICMPGE: + case ByteOps.IF_ICMPGT: + case ByteOps.IF_ICMPLE: { + machine.popArgs(frame, Type.INT, Type.INT); + break; + } + case ByteOps.IF_ACMPEQ: + case ByteOps.IF_ACMPNE: { + machine.popArgs(frame, Type.OBJECT, Type.OBJECT); + break; + } + case ByteOps.GOTO: + case ByteOps.JSR: + case ByteOps.GOTO_W: + case ByteOps.JSR_W: { + machine.clearArgs(); + break; + } + default: { + visitInvalid(opcode, offset, length); + return; + } + } + + machine.auxTargetArg(target); + machine.run(frame, offset, opcode); + } + + /** {@inheritDoc} */ + public void visitSwitch(int opcode, int offset, int length, + SwitchList cases, int padding) { + machine.popArgs(frame, Type.INT); + machine.auxIntArg(padding); + machine.auxSwitchArg(cases); + machine.run(frame, offset, opcode); + } + + /** {@inheritDoc} */ + public void visitNewarray(int offset, int length, CstType type, + ArrayList initValues) { + machine.popArgs(frame, Type.INT); + machine.auxInitValues(initValues); + machine.auxCstArg(type); + machine.run(frame, offset, ByteOps.NEWARRAY); + } + + /** {@inheritDoc} */ + public void setPreviousOffset(int offset) { + previousOffset = offset; + } + + /** {@inheritDoc} */ + public int getPreviousOffset() { + return previousOffset; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/SwitchList.java b/dexlib/src/main/java/com/android/dx/cf/code/SwitchList.java new file mode 100644 index 000000000..621d728f6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/SwitchList.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.util.IntList; +import com.android.dx.util.MutabilityControl; + +/** + * List of (value, target) mappings representing the choices of a + * {@code tableswitch} or {@code lookupswitch} instruction. It + * also holds the default target for the switch. + */ +public final class SwitchList extends MutabilityControl { + /** {@code non-null;} list of test values */ + private final IntList values; + + /** + * {@code non-null;} list of targets corresponding to the test values; there + * is always one extra element in the target list, to hold the + * default target + */ + private final IntList targets; + + /** ultimate size of the list */ + private int size; + + /** + * Constructs an instance. + * + * @param size {@code >= 0;} the number of elements to be in the table + */ + public SwitchList(int size) { + super(true); + this.values = new IntList(size); + this.targets = new IntList(size + 1); + this.size = size; + } + + /** {@inheritDoc} */ + @Override + public void setImmutable() { + values.setImmutable(); + targets.setImmutable(); + super.setImmutable(); + } + + /** + * Gets the size of the list. + * + * @return {@code >= 0;} the list size + */ + public int size() { + return size; + } + + /** + * Gets the indicated test value. + * + * @param n {@code >= 0;}, < size(); which index + * @return the test value + */ + public int getValue(int n) { + return values.get(n); + } + + /** + * Gets the indicated target. Asking for the target at {@code size()} + * returns the default target. + * + * @param n {@code >= 0, <= size();} which index + * @return {@code >= 0;} the target + */ + public int getTarget(int n) { + return targets.get(n); + } + + /** + * Gets the default target. This is just a shorthand for + * {@code getTarget(size())}. + * + * @return {@code >= 0;} the default target + */ + public int getDefaultTarget() { + return targets.get(size); + } + + /** + * Gets the list of all targets. This includes one extra element at the + * end of the list, which holds the default target. + * + * @return {@code non-null;} the target list + */ + public IntList getTargets() { + return targets; + } + + /** + * Gets the list of all case values. + * + * @return {@code non-null;} the case value list + */ + public IntList getValues() { + return values; + } + + /** + * Sets the default target. It is only valid to call this method + * when all the non-default elements have been set. + * + * @param target {@code >= 0;} the absolute (not relative) default target + * address + */ + public void setDefaultTarget(int target) { + throwIfImmutable(); + + if (target < 0) { + throw new IllegalArgumentException("target < 0"); + } + + if (targets.size() != size) { + throw new RuntimeException("non-default elements not all set"); + } + + targets.add(target); + } + + /** + * Adds the given item. + * + * @param value the test value + * @param target {@code >= 0;} the absolute (not relative) target address + */ + public void add(int value, int target) { + throwIfImmutable(); + + if (target < 0) { + throw new IllegalArgumentException("target < 0"); + } + + values.add(value); + targets.add(target); + } + + /** + * Shrinks this instance if possible, removing test elements that + * refer to the default target. This is only valid after the instance + * is fully populated, including the default target (naturally). + */ + public void removeSuperfluousDefaults() { + throwIfImmutable(); + + int sz = size; + + if (sz != (targets.size() - 1)) { + throw new IllegalArgumentException("incomplete instance"); + } + + int defaultTarget = targets.get(sz); + int at = 0; + + for (int i = 0; i < sz; i++) { + int target = targets.get(i); + if (target != defaultTarget) { + if (i != at) { + targets.set(at, target); + values.set(at, values.get(i)); + } + at++; + } + } + + if (at != sz) { + values.shrink(at); + targets.set(at, defaultTarget); + targets.shrink(at + 1); + size = at; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/ValueAwareMachine.java b/dexlib/src/main/java/com/android/dx/cf/code/ValueAwareMachine.java new file mode 100644 index 000000000..de75db55b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/ValueAwareMachine.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.code; + +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.Hex; + +/** + * {@link Machine} which keeps track of known values but does not do + * smart/realistic reference type calculations. + */ +public class ValueAwareMachine extends BaseMachine { + /** + * Constructs an instance. + * + * @param prototype {@code non-null;} the prototype for the associated + * method + */ + public ValueAwareMachine(Prototype prototype) { + super(prototype); + } + + /** {@inheritDoc} */ + public void run(Frame frame, int offset, int opcode) { + switch (opcode) { + case ByteOps.NOP: + case ByteOps.IASTORE: + case ByteOps.POP: + case ByteOps.POP2: + case ByteOps.IFEQ: + case ByteOps.IFNE: + case ByteOps.IFLT: + case ByteOps.IFGE: + case ByteOps.IFGT: + case ByteOps.IFLE: + case ByteOps.IF_ICMPEQ: + case ByteOps.IF_ICMPNE: + case ByteOps.IF_ICMPLT: + case ByteOps.IF_ICMPGE: + case ByteOps.IF_ICMPGT: + case ByteOps.IF_ICMPLE: + case ByteOps.IF_ACMPEQ: + case ByteOps.IF_ACMPNE: + case ByteOps.GOTO: + case ByteOps.RET: + case ByteOps.LOOKUPSWITCH: + case ByteOps.IRETURN: + case ByteOps.RETURN: + case ByteOps.PUTSTATIC: + case ByteOps.PUTFIELD: + case ByteOps.ATHROW: + case ByteOps.MONITORENTER: + case ByteOps.MONITOREXIT: + case ByteOps.IFNULL: + case ByteOps.IFNONNULL: { + // Nothing to do for these ops in this class. + clearResult(); + break; + } + case ByteOps.LDC: + case ByteOps.LDC2_W: { + setResult((TypeBearer) getAuxCst()); + break; + } + case ByteOps.ILOAD: + case ByteOps.ISTORE: { + setResult(arg(0)); + break; + } + case ByteOps.IALOAD: + case ByteOps.IADD: + case ByteOps.ISUB: + case ByteOps.IMUL: + case ByteOps.IDIV: + case ByteOps.IREM: + case ByteOps.INEG: + case ByteOps.ISHL: + case ByteOps.ISHR: + case ByteOps.IUSHR: + case ByteOps.IAND: + case ByteOps.IOR: + case ByteOps.IXOR: + case ByteOps.IINC: + case ByteOps.I2L: + case ByteOps.I2F: + case ByteOps.I2D: + case ByteOps.L2I: + case ByteOps.L2F: + case ByteOps.L2D: + case ByteOps.F2I: + case ByteOps.F2L: + case ByteOps.F2D: + case ByteOps.D2I: + case ByteOps.D2L: + case ByteOps.D2F: + case ByteOps.I2B: + case ByteOps.I2C: + case ByteOps.I2S: + case ByteOps.LCMP: + case ByteOps.FCMPL: + case ByteOps.FCMPG: + case ByteOps.DCMPL: + case ByteOps.DCMPG: + case ByteOps.ARRAYLENGTH: { + setResult(getAuxType()); + break; + } + case ByteOps.DUP: + case ByteOps.DUP_X1: + case ByteOps.DUP_X2: + case ByteOps.DUP2: + case ByteOps.DUP2_X1: + case ByteOps.DUP2_X2: + case ByteOps.SWAP: { + clearResult(); + for (int pattern = getAuxInt(); pattern != 0; pattern >>= 4) { + int which = (pattern & 0x0f) - 1; + addResult(arg(which)); + } + break; + } + + case ByteOps.JSR: { + setResult(new ReturnAddress(getAuxTarget())); + break; + } + case ByteOps.GETSTATIC: + case ByteOps.GETFIELD: + case ByteOps.INVOKEVIRTUAL: + case ByteOps.INVOKESTATIC: + case ByteOps.INVOKEINTERFACE: { + Type type = ((TypeBearer) getAuxCst()).getType(); + if (type == Type.VOID) { + clearResult(); + } else { + setResult(type); + } + break; + } + case ByteOps.INVOKESPECIAL: { + Type thisType = arg(0).getType(); + if (thisType.isUninitialized()) { + frame.makeInitialized(thisType); + } + Type type = ((TypeBearer) getAuxCst()).getType(); + if (type == Type.VOID) { + clearResult(); + } else { + setResult(type); + } + break; + } + case ByteOps.NEW: { + Type type = ((CstType) getAuxCst()).getClassType(); + setResult(type.asUninitialized(offset)); + break; + } + case ByteOps.NEWARRAY: + case ByteOps.CHECKCAST: + case ByteOps.MULTIANEWARRAY: { + Type type = ((CstType) getAuxCst()).getClassType(); + setResult(type); + break; + } + case ByteOps.ANEWARRAY: { + Type type = ((CstType) getAuxCst()).getClassType(); + setResult(type.getArrayType()); + break; + } + case ByteOps.INSTANCEOF: { + setResult(Type.INT); + break; + } + default: { + throw new RuntimeException("shouldn't happen: " + + Hex.u1(opcode)); + } + } + + storeResults(frame); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/code/package.html b/dexlib/src/main/java/com/android/dx/cf/code/package.html new file mode 100644 index 000000000..abd4e9bec --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/code/package.html @@ -0,0 +1,10 @@ + +

Implementation of classes having to do with Java simulation, such as +is needed for verification or stack-to-register conversion.

+ +

PACKAGES USED: +

    +
  • com.android.dx.rop.pool
  • +
  • com.android.dx.util
  • +
+ diff --git a/dexlib/src/main/java/com/android/dx/cf/cst/ConstantPoolParser.java b/dexlib/src/main/java/com/android/dx/cf/cst/ConstantPoolParser.java new file mode 100644 index 000000000..147edf3bb --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/cst/ConstantPoolParser.java @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.cst; + +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Class; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Double; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Fieldref; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Float; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Integer; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InterfaceMethodref; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Long; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Methodref; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_NameAndType; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_String; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_Utf8; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_MethodHandle; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_MethodType; +import static com.android.dx.cf.cst.ConstantTags.CONSTANT_InvokeDynamic; +import com.android.dx.cf.iface.ParseException; +import com.android.dx.cf.iface.ParseObserver; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstDouble; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstFloat; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstInterfaceMethodRef; +import com.android.dx.rop.cst.CstInvokeDynamic; +import com.android.dx.rop.cst.CstLong; +import com.android.dx.rop.cst.CstMethodHandle; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstMethodType; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.cst.StdConstantPool; +import com.android.dx.rop.type.Type; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; +import java.util.BitSet; + +/** + * Parser for a constant pool embedded in a class file. + */ +public final class ConstantPoolParser { + /** {@code non-null;} the bytes of the constant pool */ + private final ByteArray bytes; + + /** {@code non-null;} actual parsed constant pool contents */ + private final StdConstantPool pool; + + /** {@code non-null;} byte offsets to each cst */ + private final int[] offsets; + + /** + * -1 || >= 10; the end offset of this constant pool in the + * {@code byte[]} which it came from or {@code -1} if not + * yet parsed + */ + private int endOffset; + + /** {@code null-ok;} parse observer, if any */ + private ParseObserver observer; + + /** + * Constructs an instance. + * + * @param bytes {@code non-null;} the bytes of the file + */ + public ConstantPoolParser(ByteArray bytes) { + int size = bytes.getUnsignedShort(8); // constant_pool_count + + this.bytes = bytes; + this.pool = new StdConstantPool(size); + this.offsets = new int[size]; + this.endOffset = -1; + } + + /** + * Sets the parse observer for this instance. + * + * @param observer {@code null-ok;} the observer + */ + public void setObserver(ParseObserver observer) { + this.observer = observer; + } + + /** + * Gets the end offset of this constant pool in the {@code byte[]} + * which it came from. + * + * @return {@code >= 10;} the end offset + */ + public int getEndOffset() { + parseIfNecessary(); + return endOffset; + } + + /** + * Gets the actual constant pool. + * + * @return {@code non-null;} the constant pool + */ + public StdConstantPool getPool() { + parseIfNecessary(); + return pool; + } + + /** + * Runs {@link #parse} if it has not yet been run successfully. + */ + private void parseIfNecessary() { + if (endOffset < 0) { + parse(); + } + } + + /** + * Does the actual parsing. + */ + private void parse() { + determineOffsets(); + + if (observer != null) { + observer.parsed(bytes, 8, 2, + "constant_pool_count: " + Hex.u2(offsets.length)); + observer.parsed(bytes, 10, 0, "\nconstant_pool:"); + observer.changeIndent(1); + } + + /* + * Track the constant value's original string type. True if constants[i] was + * a CONSTANT_Utf8, false for any other type including CONSTANT_string. + */ + BitSet wasUtf8 = new BitSet(offsets.length); + + for (int i = 1; i < offsets.length; i++) { + int offset = offsets[i]; + if ((offset != 0) && (pool.getOrNull(i) == null)) { + parse0(i, wasUtf8); + } + } + + if (observer != null) { + for (int i = 1; i < offsets.length; i++) { + Constant cst = pool.getOrNull(i); + if (cst == null) { + continue; + } + int offset = offsets[i]; + int nextOffset = endOffset; + for (int j = i + 1; j < offsets.length; j++) { + int off = offsets[j]; + if (off != 0) { + nextOffset = off; + break; + } + } + String human = wasUtf8.get(i) + ? Hex.u2(i) + ": utf8{\"" + cst.toHuman() + "\"}" + : Hex.u2(i) + ": " + cst.toString(); + observer.parsed(bytes, offset, nextOffset - offset, human); + } + + observer.changeIndent(-1); + observer.parsed(bytes, endOffset, 0, "end constant_pool"); + } + } + + /** + * Populates {@link #offsets} and also completely parse utf8 constants. + */ + private void determineOffsets() { + int at = 10; // offset from the start of the file to the first cst + int lastCategory; + + for (int i = 1; i < offsets.length; i += lastCategory) { + offsets[i] = at; + int tag = bytes.getUnsignedByte(at); + try { + switch (tag) { + case CONSTANT_Integer: + case CONSTANT_Float: + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_NameAndType: { + lastCategory = 1; + at += 5; + break; + } + case CONSTANT_Long: + case CONSTANT_Double: { + lastCategory = 2; + at += 9; + break; + } + case CONSTANT_Class: + case CONSTANT_String: { + lastCategory = 1; + at += 3; + break; + } + case CONSTANT_Utf8: { + lastCategory = 1; + at += bytes.getUnsignedShort(at + 1) + 3; + break; + } + case CONSTANT_MethodHandle: { + lastCategory = 1; + at += 4; + break; + } + case CONSTANT_MethodType: { + lastCategory = 1; + at += 3; + break; + } + case CONSTANT_InvokeDynamic: { + lastCategory = 1; + at += 5; + break; + } + default: { + throw new ParseException("unknown tag byte: " + Hex.u1(tag)); + } + } + } catch (ParseException ex) { + ex.addContext("...while preparsing cst " + Hex.u2(i) + " at offset " + Hex.u4(at)); + throw ex; + } + } + + endOffset = at; + } + + /** + * Parses the constant for the given index if it hasn't already been + * parsed, also storing it in the constant pool. This will also + * have the side effect of parsing any entries the indicated one + * depends on. + * + * @param idx which constant + * @return {@code non-null;} the parsed constant + */ + private Constant parse0(int idx, BitSet wasUtf8) { + Constant cst = pool.getOrNull(idx); + if (cst != null) { + return cst; + } + + int at = offsets[idx]; + + try { + int tag = bytes.getUnsignedByte(at); + switch (tag) { + case CONSTANT_Utf8: { + cst = parseUtf8(at); + wasUtf8.set(idx); + break; + } + case CONSTANT_Integer: { + int value = bytes.getInt(at + 1); + cst = CstInteger.make(value); + break; + } + case CONSTANT_Float: { + int bits = bytes.getInt(at + 1); + cst = CstFloat.make(bits); + break; + } + case CONSTANT_Long: { + long value = bytes.getLong(at + 1); + cst = CstLong.make(value); + break; + } + case CONSTANT_Double: { + long bits = bytes.getLong(at + 1); + cst = CstDouble.make(bits); + break; + } + case CONSTANT_Class: { + int nameIndex = bytes.getUnsignedShort(at + 1); + CstString name = (CstString) parse0(nameIndex, wasUtf8); + cst = new CstType(Type.internClassName(name.getString())); + break; + } + case CONSTANT_String: { + int stringIndex = bytes.getUnsignedShort(at + 1); + cst = parse0(stringIndex, wasUtf8); + break; + } + case CONSTANT_Fieldref: { + int classIndex = bytes.getUnsignedShort(at + 1); + CstType type = (CstType) parse0(classIndex, wasUtf8); + int natIndex = bytes.getUnsignedShort(at + 3); + CstNat nat = (CstNat) parse0(natIndex, wasUtf8); + cst = new CstFieldRef(type, nat); + break; + } + case CONSTANT_Methodref: { + int classIndex = bytes.getUnsignedShort(at + 1); + CstType type = (CstType) parse0(classIndex, wasUtf8); + int natIndex = bytes.getUnsignedShort(at + 3); + CstNat nat = (CstNat) parse0(natIndex, wasUtf8); + cst = new CstMethodRef(type, nat); + break; + } + case CONSTANT_InterfaceMethodref: { + int classIndex = bytes.getUnsignedShort(at + 1); + CstType type = (CstType) parse0(classIndex, wasUtf8); + int natIndex = bytes.getUnsignedShort(at + 3); + CstNat nat = (CstNat) parse0(natIndex, wasUtf8); + cst = new CstInterfaceMethodRef(type, nat); + break; + } + case CONSTANT_NameAndType: { + int nameIndex = bytes.getUnsignedShort(at + 1); + CstString name = (CstString) parse0(nameIndex, wasUtf8); + int descriptorIndex = bytes.getUnsignedShort(at + 3); + CstString descriptor = (CstString) parse0(descriptorIndex, wasUtf8); + cst = new CstNat(name, descriptor); + break; + } + case CONSTANT_MethodHandle: { + int kind = bytes.getUnsignedByte(at + 1); + int constantIndex = bytes.getUnsignedShort(at + 2); + Constant ref; + switch (kind) { + case CstMethodHandle.KIND_GETFIELD: + case CstMethodHandle.KIND_GETSTATIC: + case CstMethodHandle.KIND_PUTFIELD: + case CstMethodHandle.KIND_PUTSTATIC: + CstFieldRef field = (CstFieldRef) parse0(constantIndex, wasUtf8); + ref = field; + break; + case CstMethodHandle.KIND_INVOKEVIRTUAL: + case CstMethodHandle.KIND_NEWINVOKESPECIAL: + CstMethodRef method = (CstMethodRef) parse0(constantIndex, wasUtf8); + ref = method; + break; + case CstMethodHandle.KIND_INVOKESTATIC: + case CstMethodHandle.KIND_INVOKESPECIAL: + ref = parse0(constantIndex, wasUtf8); + if (!(ref instanceof CstMethodRef + || ref instanceof CstInterfaceMethodRef)) { + throw new ParseException( + "Unsupported ref constant type for MethodHandle " + + ref.getClass()); + } + break; + case CstMethodHandle.KIND_INVOKEINTERFACE: + CstInterfaceMethodRef interfaceMethod = + (CstInterfaceMethodRef) parse0(constantIndex, wasUtf8); + ref = interfaceMethod; + break; + default: + throw new ParseException("Unsupported MethodHandle kind: " + kind); + } + cst = CstMethodHandle.make(kind, ref); + break; + } + case CONSTANT_MethodType: { + int descriptorIndex = bytes.getUnsignedShort(at + 1); + CstString descriptor = (CstString) parse0(descriptorIndex, wasUtf8); + cst = CstMethodType.make(descriptor); + break; + } + case CONSTANT_InvokeDynamic: { + int bootstrapMethodIndex = bytes.getUnsignedShort(at + 1); + int natIndex = bytes.getUnsignedShort(at + 3); + CstNat nat = (CstNat) parse0(natIndex, wasUtf8); + cst = CstInvokeDynamic.make(bootstrapMethodIndex, nat); + break; + } + default: { + throw new ParseException("unknown tag byte: " + Hex.u1(tag)); + } + } + } catch (ParseException ex) { + ex.addContext("...while parsing cst " + Hex.u2(idx) + + " at offset " + Hex.u4(at)); + throw ex; + } catch (RuntimeException ex) { + ParseException pe = new ParseException(ex); + pe.addContext("...while parsing cst " + Hex.u2(idx) + + " at offset " + Hex.u4(at)); + throw pe; + } + + pool.set(idx, cst); + return cst; + } + + /** + * Parses a utf8 constant. + * + * @param at offset to the start of the constant (where the tag byte is) + * @return {@code non-null;} the parsed value + */ + private CstString parseUtf8(int at) { + int length = bytes.getUnsignedShort(at + 1); + + at += 3; // Skip to the data. + + ByteArray ubytes = bytes.slice(at, at + length); + + try { + return new CstString(ubytes); + } catch (IllegalArgumentException ex) { + // Translate the exception + throw new ParseException(ex); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/cst/ConstantTags.java b/dexlib/src/main/java/com/android/dx/cf/cst/ConstantTags.java new file mode 100644 index 000000000..56ef4d75f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/cst/ConstantTags.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.cst; + +/** + * Tags for constant pool constants. + */ +public interface ConstantTags { + /** tag for a {@code CONSTANT_Utf8_info} */ + int CONSTANT_Utf8 = 1; + + /** tag for a {@code CONSTANT_Integer_info} */ + int CONSTANT_Integer = 3; + + /** tag for a {@code CONSTANT_Float_info} */ + int CONSTANT_Float = 4; + + /** tag for a {@code CONSTANT_Long_info} */ + int CONSTANT_Long = 5; + + /** tag for a {@code CONSTANT_Double_info} */ + int CONSTANT_Double = 6; + + /** tag for a {@code CONSTANT_Class_info} */ + int CONSTANT_Class = 7; + + /** tag for a {@code CONSTANT_String_info} */ + int CONSTANT_String = 8; + + /** tag for a {@code CONSTANT_Fieldref_info} */ + int CONSTANT_Fieldref = 9; + + /** tag for a {@code CONSTANT_Methodref_info} */ + int CONSTANT_Methodref = 10; + + /** tag for a {@code CONSTANT_InterfaceMethodref_info} */ + int CONSTANT_InterfaceMethodref = 11; + + /** tag for a {@code CONSTANT_NameAndType_info} */ + int CONSTANT_NameAndType = 12; + + /** tag for a {@code CONSTANT_MethodHandle} */ + int CONSTANT_MethodHandle = 15; + + /** tag for a {@code CONSTANT_MethodType} */ + int CONSTANT_MethodType = 16; + + /** tag for a {@code CONSTANT_InvokeDynamic} */ + int CONSTANT_InvokeDynamic = 18; +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/AnnotationParser.java b/dexlib/src/main/java/com/android/dx/cf/direct/AnnotationParser.java new file mode 100644 index 000000000..f76669445 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/AnnotationParser.java @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dx.cf.iface.ParseException; +import com.android.dx.cf.iface.ParseObserver; +import com.android.dx.rop.annotation.Annotation; +import com.android.dx.rop.annotation.AnnotationVisibility; +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.rop.annotation.NameValuePair; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.ConstantPool; +import com.android.dx.rop.cst.CstAnnotation; +import com.android.dx.rop.cst.CstArray; +import com.android.dx.rop.cst.CstBoolean; +import com.android.dx.rop.cst.CstByte; +import com.android.dx.rop.cst.CstChar; +import com.android.dx.rop.cst.CstDouble; +import com.android.dx.rop.cst.CstEnumRef; +import com.android.dx.rop.cst.CstFloat; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstLong; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstShort; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; +import java.io.IOException; + +/** + * Parser for annotations. + */ +public final class AnnotationParser { + /** {@code non-null;} class file being parsed */ + private final DirectClassFile cf; + + /** {@code non-null;} constant pool to use */ + private final ConstantPool pool; + + /** {@code non-null;} bytes of the attribute data */ + private final ByteArray bytes; + + /** {@code null-ok;} parse observer, if any */ + private final ParseObserver observer; + + /** {@code non-null;} input stream to parse from */ + private final ByteArray.MyDataInputStream input; + + /** + * {@code non-null;} cursor for use when informing the observer of what + * was parsed + */ + private int parseCursor; + + /** + * Constructs an instance. + * + * @param cf {@code non-null;} class file to parse from + * @param offset {@code >= 0;} offset into the class file data to parse at + * @param length {@code >= 0;} number of bytes left in the attribute data + * @param observer {@code null-ok;} parse observer to notify, if any + */ + public AnnotationParser(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + if (cf == null) { + throw new NullPointerException("cf == null"); + } + + this.cf = cf; + this.pool = cf.getConstantPool(); + this.observer = observer; + this.bytes = cf.getBytes().slice(offset, offset + length); + this.input = bytes.makeDataInputStream(); + this.parseCursor = 0; + } + + /** + * Parses an annotation value ({@code element_value}) attribute. + * + * @return {@code non-null;} the parsed constant value + */ + public Constant parseValueAttribute() { + Constant result; + + try { + result = parseValue(); + + if (input.available() != 0) { + throw new ParseException("extra data in attribute"); + } + } catch (IOException ex) { + // ByteArray.MyDataInputStream should never throw. + throw new RuntimeException("shouldn't happen", ex); + } + + return result; + } + + /** + * Parses a parameter annotation attribute. + * + * @param visibility {@code non-null;} visibility of the parsed annotations + * @return {@code non-null;} the parsed list of lists of annotations + */ + public AnnotationsList parseParameterAttribute( + AnnotationVisibility visibility) { + AnnotationsList result; + + try { + result = parseAnnotationsList(visibility); + + if (input.available() != 0) { + throw new ParseException("extra data in attribute"); + } + } catch (IOException ex) { + // ByteArray.MyDataInputStream should never throw. + throw new RuntimeException("shouldn't happen", ex); + } + + return result; + } + + /** + * Parses an annotation attribute, per se. + * + * @param visibility {@code non-null;} visibility of the parsed annotations + * @return {@code non-null;} the list of annotations read from the attribute + * data + */ + public Annotations parseAnnotationAttribute( + AnnotationVisibility visibility) { + Annotations result; + + try { + result = parseAnnotations(visibility); + + if (input.available() != 0) { + throw new ParseException("extra data in attribute"); + } + } catch (IOException ex) { + // ByteArray.MyDataInputStream should never throw. + throw new RuntimeException("shouldn't happen", ex); + } + + return result; + } + + /** + * Parses a list of annotation lists. + * + * @param visibility {@code non-null;} visibility of the parsed annotations + * @return {@code non-null;} the list of annotation lists read from the attribute + * data + */ + private AnnotationsList parseAnnotationsList( + AnnotationVisibility visibility) throws IOException { + int count = input.readUnsignedByte(); + + if (observer != null) { + parsed(1, "num_parameters: " + Hex.u1(count)); + } + + AnnotationsList outerList = new AnnotationsList(count); + + for (int i = 0; i < count; i++) { + if (observer != null) { + parsed(0, "parameter_annotations[" + i + "]:"); + changeIndent(1); + } + + Annotations annotations = parseAnnotations(visibility); + outerList.set(i, annotations); + + if (observer != null) { + observer.changeIndent(-1); + } + } + + outerList.setImmutable(); + return outerList; + } + + /** + * Parses an annotation list. + * + * @param visibility {@code non-null;} visibility of the parsed annotations + * @return {@code non-null;} the list of annotations read from the attribute + * data + */ + private Annotations parseAnnotations(AnnotationVisibility visibility) + throws IOException { + int count = input.readUnsignedShort(); + + if (observer != null) { + parsed(2, "num_annotations: " + Hex.u2(count)); + } + + Annotations annotations = new Annotations(); + + for (int i = 0; i < count; i++) { + if (observer != null) { + parsed(0, "annotations[" + i + "]:"); + changeIndent(1); + } + + Annotation annotation = parseAnnotation(visibility); + annotations.add(annotation); + + if (observer != null) { + observer.changeIndent(-1); + } + } + + annotations.setImmutable(); + return annotations; + } + + /** + * Parses a single annotation. + * + * @param visibility {@code non-null;} visibility of the parsed annotation + * @return {@code non-null;} the parsed annotation + */ + private Annotation parseAnnotation(AnnotationVisibility visibility) + throws IOException { + requireLength(4); + + int typeIndex = input.readUnsignedShort(); + int numElements = input.readUnsignedShort(); + CstString typeString = (CstString) pool.get(typeIndex); + CstType type = new CstType(Type.intern(typeString.getString())); + + if (observer != null) { + parsed(2, "type: " + type.toHuman()); + parsed(2, "num_elements: " + numElements); + } + + Annotation annotation = new Annotation(type, visibility); + + for (int i = 0; i < numElements; i++) { + if (observer != null) { + parsed(0, "elements[" + i + "]:"); + changeIndent(1); + } + + NameValuePair element = parseElement(); + annotation.add(element); + + if (observer != null) { + changeIndent(-1); + } + } + + annotation.setImmutable(); + return annotation; + } + + /** + * Parses a {@link NameValuePair}. + * + * @return {@code non-null;} the parsed element + */ + private NameValuePair parseElement() throws IOException { + requireLength(5); + + int elementNameIndex = input.readUnsignedShort(); + CstString elementName = (CstString) pool.get(elementNameIndex); + + if (observer != null) { + parsed(2, "element_name: " + elementName.toHuman()); + parsed(0, "value: "); + changeIndent(1); + } + + Constant value = parseValue(); + + if (observer != null) { + changeIndent(-1); + } + + return new NameValuePair(elementName, value); + } + + /** + * Parses an annotation value. + * + * @return {@code non-null;} the parsed value + */ + private Constant parseValue() throws IOException { + int tag = input.readUnsignedByte(); + + if (observer != null) { + CstString humanTag = new CstString(Character.toString((char) tag)); + parsed(1, "tag: " + humanTag.toQuoted()); + } + + switch (tag) { + case 'B': { + CstInteger value = (CstInteger) parseConstant(); + return CstByte.make(value.getValue()); + } + case 'C': { + CstInteger value = (CstInteger) parseConstant(); + int intValue = value.getValue(); + return CstChar.make(value.getValue()); + } + case 'D': { + CstDouble value = (CstDouble) parseConstant(); + return value; + } + case 'F': { + CstFloat value = (CstFloat) parseConstant(); + return value; + } + case 'I': { + CstInteger value = (CstInteger) parseConstant(); + return value; + } + case 'J': { + CstLong value = (CstLong) parseConstant(); + return value; + } + case 'S': { + CstInteger value = (CstInteger) parseConstant(); + return CstShort.make(value.getValue()); + } + case 'Z': { + CstInteger value = (CstInteger) parseConstant(); + return CstBoolean.make(value.getValue()); + } + case 'c': { + int classInfoIndex = input.readUnsignedShort(); + CstString value = (CstString) pool.get(classInfoIndex); + Type type = Type.internReturnType(value.getString()); + + if (observer != null) { + parsed(2, "class_info: " + type.toHuman()); + } + + return new CstType(type); + } + case 's': { + return parseConstant(); + } + case 'e': { + requireLength(4); + + int typeNameIndex = input.readUnsignedShort(); + int constNameIndex = input.readUnsignedShort(); + CstString typeName = (CstString) pool.get(typeNameIndex); + CstString constName = (CstString) pool.get(constNameIndex); + + if (observer != null) { + parsed(2, "type_name: " + typeName.toHuman()); + parsed(2, "const_name: " + constName.toHuman()); + } + + return new CstEnumRef(new CstNat(constName, typeName)); + } + case '@': { + Annotation annotation = + parseAnnotation(AnnotationVisibility.EMBEDDED); + return new CstAnnotation(annotation); + } + case '[': { + requireLength(2); + + int numValues = input.readUnsignedShort(); + CstArray.List list = new CstArray.List(numValues); + + if (observer != null) { + parsed(2, "num_values: " + numValues); + changeIndent(1); + } + + for (int i = 0; i < numValues; i++) { + if (observer != null) { + changeIndent(-1); + parsed(0, "element_value[" + i + "]:"); + changeIndent(1); + } + list.set(i, parseValue()); + } + + if (observer != null) { + changeIndent(-1); + } + + list.setImmutable(); + return new CstArray(list); + } + default: { + throw new ParseException("unknown annotation tag: " + + Hex.u1(tag)); + } + } + } + + /** + * Helper for {@link #parseValue}, which parses a constant reference + * and returns the referred-to constant value. + * + * @return {@code non-null;} the parsed value + */ + private Constant parseConstant() throws IOException { + int constValueIndex = input.readUnsignedShort(); + Constant value = (Constant) pool.get(constValueIndex); + + if (observer != null) { + String human = (value instanceof CstString) + ? ((CstString) value).toQuoted() + : value.toHuman(); + parsed(2, "constant_value: " + human); + } + + return value; + } + + /** + * Helper which will throw an exception if the given number of bytes + * is not available to be read. + * + * @param requiredLength the number of required bytes + */ + private void requireLength(int requiredLength) throws IOException { + if (input.available() < requiredLength) { + throw new ParseException("truncated annotation attribute"); + } + } + + /** + * Helper which indicates that some bytes were just parsed. This should + * only be used (for efficiency sake) if the parse is known to be + * observed. + * + * @param length {@code >= 0;} number of bytes parsed + * @param message {@code non-null;} associated message + */ + private void parsed(int length, String message) { + observer.parsed(bytes, parseCursor, length, message); + parseCursor += length; + } + + /** + * Convenience wrapper that simply calls through to + * {@code observer.changeIndent()}. + * + * @param indent the amount to change the indent by + */ + private void changeIndent(int indent) { + observer.changeIndent(indent); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/AttributeFactory.java b/dexlib/src/main/java/com/android/dx/cf/direct/AttributeFactory.java new file mode 100644 index 000000000..f7486eb9c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/AttributeFactory.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dx.cf.attrib.RawAttribute; +import com.android.dx.cf.iface.Attribute; +import com.android.dx.cf.iface.ParseException; +import com.android.dx.cf.iface.ParseObserver; +import com.android.dx.rop.cst.ConstantPool; +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; + +/** + * Factory capable of instantiating various {@link Attribute} subclasses + * depending on the context and name. + */ +public class AttributeFactory { + /** context for attributes on class files */ + public static final int CTX_CLASS = 0; + + /** context for attributes on fields */ + public static final int CTX_FIELD = 1; + + /** context for attributes on methods */ + public static final int CTX_METHOD = 2; + + /** context for attributes on code attributes */ + public static final int CTX_CODE = 3; + + /** number of contexts */ + public static final int CTX_COUNT = 4; + + /** + * Constructs an instance. + */ + public AttributeFactory() { + // This space intentionally left blank. + } + + /** + * Parses and makes an attribute based on the bytes at the + * indicated position in the given array. This method figures out + * the name, and then does all the setup to call on to {@link #parse0}, + * which does the actual construction. + * + * @param cf {@code non-null;} class file to parse from + * @param context context to parse in; one of the {@code CTX_*} + * constants + * @param offset offset into {@code dcf}'s {@code bytes} + * to start parsing at + * @param observer {@code null-ok;} parse observer to report to, if any + * @return {@code non-null;} an appropriately-constructed {@link Attribute} + */ + public final Attribute parse(DirectClassFile cf, int context, int offset, + ParseObserver observer) { + if (cf == null) { + throw new NullPointerException("cf == null"); + } + + if ((context < 0) || (context >= CTX_COUNT)) { + throw new IllegalArgumentException("bad context"); + } + + CstString name = null; + + try { + ByteArray bytes = cf.getBytes(); + ConstantPool pool = cf.getConstantPool(); + int nameIdx = bytes.getUnsignedShort(offset); + int length = bytes.getInt(offset + 2); + + name = (CstString) pool.get(nameIdx); + + if (observer != null) { + observer.parsed(bytes, offset, 2, + "name: " + name.toHuman()); + observer.parsed(bytes, offset + 2, 4, + "length: " + Hex.u4(length)); + } + + return parse0(cf, context, name.getString(), offset + 6, length, + observer); + } catch (ParseException ex) { + ex.addContext("...while parsing " + + ((name != null) ? (name.toHuman() + " ") : "") + + "attribute at offset " + Hex.u4(offset)); + throw ex; + } + } + + /** + * Parses attribute content. The base class implements this by constructing + * an instance of {@link RawAttribute}. Subclasses are expected to + * override this to do something better in most cases. + * + * @param cf {@code non-null;} class file to parse from + * @param context context to parse in; one of the {@code CTX_*} + * constants + * @param name {@code non-null;} the attribute name + * @param offset offset into {@code bytes} to start parsing at; this + * is the offset to the start of attribute data, not to the header + * @param length the length of the attribute data + * @param observer {@code null-ok;} parse observer to report to, if any + * @return {@code non-null;} an appropriately-constructed {@link Attribute} + */ + protected Attribute parse0(DirectClassFile cf, int context, String name, + int offset, int length, + ParseObserver observer) { + ByteArray bytes = cf.getBytes(); + ConstantPool pool = cf.getConstantPool(); + Attribute result = new RawAttribute(name, bytes, offset, length, pool); + + if (observer != null) { + observer.parsed(bytes, offset, length, "attribute data"); + } + + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/AttributeListParser.java b/dexlib/src/main/java/com/android/dx/cf/direct/AttributeListParser.java new file mode 100644 index 000000000..2715e6a94 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/AttributeListParser.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dx.cf.iface.Attribute; +import com.android.dx.cf.iface.ParseException; +import com.android.dx.cf.iface.ParseObserver; +import com.android.dx.cf.iface.StdAttributeList; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; + +/** + * Parser for lists of attributes. + */ +final /*package*/ class AttributeListParser { + /** {@code non-null;} the class file to parse from */ + private final DirectClassFile cf; + + /** attribute parsing context */ + private final int context; + + /** offset in the byte array of the classfile to the start of the list */ + private final int offset; + + /** {@code non-null;} attribute factory to use */ + private final AttributeFactory attributeFactory; + + /** {@code non-null;} list of parsed attributes */ + private final StdAttributeList list; + + /** {@code >= -1;} the end offset of this list in the byte array of the + * classfile, or {@code -1} if not yet parsed */ + private int endOffset; + + /** {@code null-ok;} parse observer, if any */ + private ParseObserver observer; + + /** + * Constructs an instance. + * + * @param cf {@code non-null;} class file to parse from + * @param context attribute parsing context (see {@link AttributeFactory}) + * @param offset offset in {@code bytes} to the start of the list + * @param attributeFactory {@code non-null;} attribute factory to use + */ + public AttributeListParser(DirectClassFile cf, int context, int offset, + AttributeFactory attributeFactory) { + if (cf == null) { + throw new NullPointerException("cf == null"); + } + + if (attributeFactory == null) { + throw new NullPointerException("attributeFactory == null"); + } + + int size = cf.getBytes().getUnsignedShort(offset); + + this.cf = cf; + this.context = context; + this.offset = offset; + this.attributeFactory = attributeFactory; + this.list = new StdAttributeList(size); + this.endOffset = -1; + } + + /** + * Sets the parse observer for this instance. + * + * @param observer {@code null-ok;} the observer + */ + public void setObserver(ParseObserver observer) { + this.observer = observer; + } + + /** + * Gets the end offset of this constant pool in the {@code byte[]} + * which it came from. + * + * @return {@code >= 0;} the end offset + */ + public int getEndOffset() { + parseIfNecessary(); + return endOffset; + } + + /** + * Gets the parsed list. + * + * @return {@code non-null;} the list + */ + public StdAttributeList getList() { + parseIfNecessary(); + return list; + } + + /** + * Runs {@link #parse} if it has not yet been run successfully. + */ + private void parseIfNecessary() { + if (endOffset < 0) { + parse(); + } + } + + /** + * Does the actual parsing. + */ + private void parse() { + int sz = list.size(); + int at = offset + 2; // Skip the count. + + ByteArray bytes = cf.getBytes(); + + if (observer != null) { + observer.parsed(bytes, offset, 2, + "attributes_count: " + Hex.u2(sz)); + } + + for (int i = 0; i < sz; i++) { + try { + if (observer != null) { + observer.parsed(bytes, at, 0, + "\nattributes[" + i + "]:\n"); + observer.changeIndent(1); + } + + Attribute attrib = + attributeFactory.parse(cf, context, at, observer); + + at += attrib.byteLength(); + list.set(i, attrib); + + if (observer != null) { + observer.changeIndent(-1); + observer.parsed(bytes, at, 0, + "end attributes[" + i + "]\n"); + } + } catch (ParseException ex) { + ex.addContext("...while parsing attributes[" + i + "]"); + throw ex; + } catch (RuntimeException ex) { + ParseException pe = new ParseException(ex); + pe.addContext("...while parsing attributes[" + i + "]"); + throw pe; + } + } + + endOffset = at; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/ClassPathOpener.java b/dexlib/src/main/java/com/android/dx/cf/direct/ClassPathOpener.java new file mode 100644 index 000000000..26fbca029 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/ClassPathOpener.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dex.util.FileUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Opens all the class files found in a class path element. Path elements + * can point to class files, {jar,zip,apk} files, or directories containing + * class files. + */ +public class ClassPathOpener { + + /** {@code non-null;} pathname to start with */ + private final String pathname; + /** {@code non-null;} callback interface */ + private final Consumer consumer; + /** + * If true, sort such that classes appear before their inner + * classes and "package-info" occurs before all other classes in that + * package. + */ + private final boolean sort; + private FileNameFilter filter; + + /** + * Callback interface for {@code ClassOpener}. + */ + public interface Consumer { + + /** + * Provides the file name and byte array for a class path element. + * + * @param name {@code non-null;} filename of element. May not be a valid + * filesystem path. + * + * @param lastModified milliseconds since 1970-Jan-1 00:00:00 GMT + * @param bytes {@code non-null;} file data + * @return true on success. Result is or'd with all other results + * from {@code processFileBytes} and returned to the caller + * of {@code process()}. + */ + boolean processFileBytes(String name, long lastModified, byte[] bytes); + + /** + * Informs consumer that an exception occurred while processing + * this path element. Processing will continue if possible. + * + * @param ex {@code non-null;} exception + */ + void onException(Exception ex); + + /** + * Informs consumer that processing of an archive file has begun. + * + * @param file {@code non-null;} archive file being processed + */ + void onProcessArchiveStart(File file); + } + + /** + * Filter interface for {@code ClassOpener}. + */ + public interface FileNameFilter { + + boolean accept(String path); + } + + /** + * An accept all filter. + */ + public static final FileNameFilter acceptAll = new FileNameFilter() { + + @Override + public boolean accept(String path) { + return true; + } + }; + + /** + * Constructs an instance. + * + * @param pathname {@code non-null;} path element to process + * @param sort if true, sort such that classes appear before their inner + * classes and "package-info" occurs before all other classes in that + * package. + * @param consumer {@code non-null;} callback interface + */ + public ClassPathOpener(String pathname, boolean sort, Consumer consumer) { + this(pathname, sort, acceptAll, consumer); + } + + /** + * Constructs an instance. + * + * @param pathname {@code non-null;} path element to process + * @param sort if true, sort such that classes appear before their inner + * classes and "package-info" occurs before all other classes in that + * package. + * @param consumer {@code non-null;} callback interface + */ + public ClassPathOpener(String pathname, boolean sort, FileNameFilter filter, + Consumer consumer) { + this.pathname = pathname; + this.sort = sort; + this.consumer = consumer; + this.filter = filter; + } + + /** + * Processes a path element. + * + * @return the OR of all return values + * from {@code Consumer.processFileBytes()}. + */ + public boolean process() { + File file = new File(pathname); + + return processOne(file, true); + } + + /** + * Processes one file. + * + * @param file {@code non-null;} the file to process + * @param topLevel whether this is a top-level file (that is, + * specified directly on the commandline) + * @return whether any processing actually happened + */ + private boolean processOne(File file, boolean topLevel) { + try { + if (file.isDirectory()) { + return processDirectory(file, topLevel); + } + + String path = file.getPath(); + + if (path.endsWith(".zip") || + path.endsWith(".jar") || + path.endsWith(".apk")) { + return processArchive(file); + } + if (filter.accept(path)) { + byte[] bytes = FileUtils.readFile(file); + return consumer.processFileBytes(path, file.lastModified(), bytes); + } else { + return false; + } + } catch (Exception ex) { + consumer.onException(ex); + return false; + } + } + + /** + * Sorts java class names such that outer classes preceed their inner + * classes and "package-info" preceeds all other classes in its package. + * + * @param a {@code non-null;} first class name + * @param b {@code non-null;} second class name + * @return {@code compareTo()}-style result + */ + private static int compareClassNames(String a, String b) { + // Ensure inner classes sort second + a = a.replace('$','0'); + b = b.replace('$','0'); + + /* + * Assuming "package-info" only occurs at the end, ensures package-info + * sorts first. + */ + a = a.replace("package-info", ""); + b = b.replace("package-info", ""); + + return a.compareTo(b); + } + + /** + * Processes a directory recursively. + * + * @param dir {@code non-null;} file representing the directory + * @param topLevel whether this is a top-level directory (that is, + * specified directly on the commandline) + * @return whether any processing actually happened + */ + private boolean processDirectory(File dir, boolean topLevel) { + if (topLevel) { + dir = new File(dir, "."); + } + + File[] files = dir.listFiles(); + int len = files.length; + boolean any = false; + + if (sort) { + Arrays.sort(files, new Comparator() { + public int compare(File a, File b) { + return compareClassNames(a.getName(), b.getName()); + } + }); + } + + for (int i = 0; i < len; i++) { + any |= processOne(files[i], false); + } + + return any; + } + + /** + * Processes the contents of an archive ({@code .zip}, + * {@code .jar}, or {@code .apk}). + * + * @param file {@code non-null;} archive file to process + * @return whether any processing actually happened + * @throws IOException on i/o problem + */ + private boolean processArchive(File file) throws IOException { + ZipFile zip = new ZipFile(file); + + ArrayList entriesList + = Collections.list(zip.entries()); + + if (sort) { + Collections.sort(entriesList, new Comparator() { + public int compare (ZipEntry a, ZipEntry b) { + return compareClassNames(a.getName(), b.getName()); + } + }); + } + + consumer.onProcessArchiveStart(file); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(40000); + byte[] buf = new byte[20000]; + boolean any = false; + + for (ZipEntry one : entriesList) { + final boolean isDirectory = one.isDirectory(); + + String path = one.getName(); + if (filter.accept(path)) { + final byte[] bytes; + if (!isDirectory) { + InputStream in = zip.getInputStream(one); + + baos.reset(); + int read; + while ((read = in.read(buf)) != -1) { + baos.write(buf, 0, read); + } + + in.close(); + bytes = baos.toByteArray(); + } else { + bytes = new byte[0]; + } + + any |= consumer.processFileBytes(path, one.getTime(), bytes); + } + } + + zip.close(); + return any; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/CodeObserver.java b/dexlib/src/main/java/com/android/dx/cf/direct/CodeObserver.java new file mode 100644 index 000000000..4262cf82c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/CodeObserver.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dx.cf.code.ByteOps; +import com.android.dx.cf.code.BytecodeArray; +import com.android.dx.cf.code.SwitchList; +import com.android.dx.cf.iface.ParseObserver; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstDouble; +import com.android.dx.rop.cst.CstFloat; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstKnownNull; +import com.android.dx.rop.cst.CstLong; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; +import java.util.ArrayList; + +/** + * Bytecode visitor to use when "observing" bytecode getting parsed. + */ +public class CodeObserver implements BytecodeArray.Visitor { + /** {@code non-null;} actual array of bytecode */ + private final ByteArray bytes; + + /** {@code non-null;} observer to inform of parsing */ + private final ParseObserver observer; + + /** + * Constructs an instance. + * + * @param bytes {@code non-null;} actual array of bytecode + * @param observer {@code non-null;} observer to inform of parsing + */ + public CodeObserver(ByteArray bytes, ParseObserver observer) { + if (bytes == null) { + throw new NullPointerException("bytes == null"); + } + + if (observer == null) { + throw new NullPointerException("observer == null"); + } + + this.bytes = bytes; + this.observer = observer; + } + + /** {@inheritDoc} */ + public void visitInvalid(int opcode, int offset, int length) { + observer.parsed(bytes, offset, length, header(offset)); + } + + /** {@inheritDoc} */ + public void visitNoArgs(int opcode, int offset, int length, Type type) { + observer.parsed(bytes, offset, length, header(offset)); + } + + /** {@inheritDoc} */ + public void visitLocal(int opcode, int offset, int length, + int idx, Type type, int value) { + String idxStr = (length <= 3) ? Hex.u1(idx) : Hex.u2(idx); + boolean argComment = (length == 1); + String valueStr = ""; + + if (opcode == ByteOps.IINC) { + valueStr = ", #" + + ((length <= 3) ? Hex.s1(value) : Hex.s2(value)); + } + + String catStr = ""; + if (type.isCategory2()) { + catStr = (argComment ? "," : " //") + " category-2"; + } + + observer.parsed(bytes, offset, length, + header(offset) + (argComment ? " // " : " ") + + idxStr + valueStr + catStr); + } + + /** {@inheritDoc} */ + public void visitConstant(int opcode, int offset, int length, + Constant cst, int value) { + if (cst instanceof CstKnownNull) { + // This is aconst_null. + visitNoArgs(opcode, offset, length, null); + return; + } + + if (cst instanceof CstInteger) { + visitLiteralInt(opcode, offset, length, value); + return; + } + + if (cst instanceof CstLong) { + visitLiteralLong(opcode, offset, length, + ((CstLong) cst).getValue()); + return; + } + + if (cst instanceof CstFloat) { + visitLiteralFloat(opcode, offset, length, + ((CstFloat) cst).getIntBits()); + return; + } + + if (cst instanceof CstDouble) { + visitLiteralDouble(opcode, offset, length, + ((CstDouble) cst).getLongBits()); + return; + } + + String valueStr = ""; + if (value != 0) { + valueStr = ", "; + if (opcode == ByteOps.MULTIANEWARRAY) { + valueStr += Hex.u1(value); + } else { + valueStr += Hex.u2(value); + } + } + + observer.parsed(bytes, offset, length, + header(offset) + " " + cst + valueStr); + } + + /** {@inheritDoc} */ + public void visitBranch(int opcode, int offset, int length, + int target) { + String targetStr = (length <= 3) ? Hex.u2(target) : Hex.u4(target); + observer.parsed(bytes, offset, length, + header(offset) + " " + targetStr); + } + + /** {@inheritDoc} */ + public void visitSwitch(int opcode, int offset, int length, + SwitchList cases, int padding) { + int sz = cases.size(); + StringBuffer sb = new StringBuffer(sz * 20 + 100); + + sb.append(header(offset)); + if (padding != 0) { + sb.append(" // padding: " + Hex.u4(padding)); + } + sb.append('\n'); + + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(Hex.s4(cases.getValue(i))); + sb.append(": "); + sb.append(Hex.u2(cases.getTarget(i))); + sb.append('\n'); + } + + sb.append(" default: "); + sb.append(Hex.u2(cases.getDefaultTarget())); + + observer.parsed(bytes, offset, length, sb.toString()); + } + + /** {@inheritDoc} */ + public void visitNewarray(int offset, int length, CstType cst, + ArrayList intVals) { + String commentOrSpace = (length == 1) ? " // " : " "; + String typeName = cst.getClassType().getComponentType().toHuman(); + + observer.parsed(bytes, offset, length, + header(offset) + commentOrSpace + typeName); + } + + /** {@inheritDoc} */ + public void setPreviousOffset(int offset) { + // Do nothing + } + + /** {@inheritDoc} */ + public int getPreviousOffset() { + return -1; + } + + /** + * Helper to produce the first bit of output for each instruction. + * + * @param offset the offset to the start of the instruction + */ + private String header(int offset) { + /* + * Note: This uses the original bytecode, not the + * possibly-transformed one. + */ + int opcode = bytes.getUnsignedByte(offset); + String name = ByteOps.opName(opcode); + + if (opcode == ByteOps.WIDE) { + opcode = bytes.getUnsignedByte(offset + 1); + name += " " + ByteOps.opName(opcode); + } + + return Hex.u2(offset) + ": " + name; + } + + /** + * Helper for {@link #visitConstant} where the constant is an + * {@code int}. + * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length instruction length + * @param value constant value + */ + private void visitLiteralInt(int opcode, int offset, int length, + int value) { + String commentOrSpace = (length == 1) ? " // " : " "; + String valueStr; + + opcode = bytes.getUnsignedByte(offset); // Compare with orig op below. + if ((length == 1) || (opcode == ByteOps.BIPUSH)) { + valueStr = "#" + Hex.s1(value); + } else if (opcode == ByteOps.SIPUSH) { + valueStr = "#" + Hex.s2(value); + } else { + valueStr = "#" + Hex.s4(value); + } + + observer.parsed(bytes, offset, length, + header(offset) + commentOrSpace + valueStr); + } + + /** + * Helper for {@link #visitConstant} where the constant is a + * {@code long}. + * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length instruction length + * @param value constant value + */ + private void visitLiteralLong(int opcode, int offset, int length, + long value) { + String commentOrLit = (length == 1) ? " // " : " #"; + String valueStr; + + if (length == 1) { + valueStr = Hex.s1((int) value); + } else { + valueStr = Hex.s8(value); + } + + observer.parsed(bytes, offset, length, + header(offset) + commentOrLit + valueStr); + } + + /** + * Helper for {@link #visitConstant} where the constant is a + * {@code float}. + * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length instruction length + * @param bits constant value, as float-bits + */ + private void visitLiteralFloat(int opcode, int offset, int length, + int bits) { + String optArg = (length != 1) ? " #" + Hex.u4(bits) : ""; + + observer.parsed(bytes, offset, length, + header(offset) + optArg + " // " + + Float.intBitsToFloat(bits)); + } + + /** + * Helper for {@link #visitConstant} where the constant is a + * {@code double}. + * + * @param opcode the opcode + * @param offset offset to the instruction + * @param length instruction length + * @param bits constant value, as double-bits + */ + private void visitLiteralDouble(int opcode, int offset, int length, + long bits) { + String optArg = (length != 1) ? " #" + Hex.u8(bits) : ""; + + observer.parsed(bytes, offset, length, + header(offset) + optArg + " // " + + Double.longBitsToDouble(bits)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/DirectClassFile.java b/dexlib/src/main/java/com/android/dx/cf/direct/DirectClassFile.java new file mode 100644 index 000000000..043da75a3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/DirectClassFile.java @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dx.cf.attrib.AttSourceFile; +import com.android.dx.cf.cst.ConstantPoolParser; +import com.android.dx.cf.iface.Attribute; +import com.android.dx.cf.iface.AttributeList; +import com.android.dx.cf.iface.ClassFile; +import com.android.dx.cf.iface.FieldList; +import com.android.dx.cf.iface.MethodList; +import com.android.dx.cf.iface.ParseException; +import com.android.dx.cf.iface.ParseObserver; +import com.android.dx.cf.iface.StdAttributeList; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.cst.ConstantPool; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.cst.StdConstantPool; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; + +/** + * Class file with info taken from a {@code byte[]} or slice thereof. + */ +public class DirectClassFile implements ClassFile { + /** the expected value of the ClassFile.magic field */ + private static final int CLASS_FILE_MAGIC = 0xcafebabe; + + /** + * minimum {@code .class} file major version + * + * See http://en.wikipedia.org/wiki/Java_class_file for an up-to-date + * list of version numbers. Currently known (taken from that table) are: + * + * J2SE 7.0 = 51 (0x33 hex), + * J2SE 6.0 = 50 (0x32 hex), + * J2SE 5.0 = 49 (0x31 hex), + * JDK 1.4 = 48 (0x30 hex), + * JDK 1.3 = 47 (0x2F hex), + * JDK 1.2 = 46 (0x2E hex), + * JDK 1.1 = 45 (0x2D hex). + * + * Valid ranges are typically of the form + * "A.0 through B.C inclusive" where A <= B and C >= 0, + * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION. + */ + private static final int CLASS_FILE_MIN_MAJOR_VERSION = 45; + + /** + * maximum {@code .class} file major version + * + * Note: if you change this, please change "java.class.version" in System.java. + */ + private static final int CLASS_FILE_MAX_MAJOR_VERSION = 52; + + /** maximum {@code .class} file minor version */ + private static final int CLASS_FILE_MAX_MINOR_VERSION = 0; + + /** + * {@code non-null;} the file path for the class, excluding any base directory + * specification + */ + private final String filePath; + + /** {@code non-null;} the bytes of the file */ + private final ByteArray bytes; + + /** + * whether to be strict about parsing; if + * {@code false}, this avoids doing checks that only exist + * for purposes of verification (such as magic number matching and + * path-package consistency checking) + */ + private final boolean strictParse; + + /** + * {@code null-ok;} the constant pool; only ever {@code null} + * before the constant pool is successfully parsed + */ + private StdConstantPool pool; + + /** + * the class file field {@code access_flags}; will be {@code -1} + * before the file is successfully parsed + */ + private int accessFlags; + + /** + * {@code null-ok;} the class file field {@code this_class}, + * interpreted as a type constant; only ever {@code null} + * before the file is successfully parsed + */ + private CstType thisClass; + + /** + * {@code null-ok;} the class file field {@code super_class}, interpreted + * as a type constant if non-zero + */ + private CstType superClass; + + /** + * {@code null-ok;} the class file field {@code interfaces}; only + * ever {@code null} before the file is successfully + * parsed + */ + private TypeList interfaces; + + /** + * {@code null-ok;} the class file field {@code fields}; only ever + * {@code null} before the file is successfully parsed + */ + private FieldList fields; + + /** + * {@code null-ok;} the class file field {@code methods}; only ever + * {@code null} before the file is successfully parsed + */ + private MethodList methods; + + /** + * {@code null-ok;} the class file field {@code attributes}; only + * ever {@code null} before the file is successfully + * parsed + */ + private StdAttributeList attributes; + + /** {@code null-ok;} attribute factory, if any */ + private AttributeFactory attributeFactory; + + /** {@code null-ok;} parse observer, if any */ + private ParseObserver observer; + + /** + * Returns the string form of an object or {@code "(none)"} + * (rather than {@code "null"}) for {@code null}. + * + * @param obj {@code null-ok;} the object to stringify + * @return {@code non-null;} the appropriate string form + */ + public static String stringOrNone(Object obj) { + if (obj == null) { + return "(none)"; + } + + return obj.toString(); + } + + /** + * Constructs an instance. + * + * @param bytes {@code non-null;} the bytes of the file + * @param filePath {@code non-null;} the file path for the class, + * excluding any base directory specification + * @param strictParse whether to be strict about parsing; if + * {@code false}, this avoids doing checks that only exist + * for purposes of verification (such as magic number matching and + * path-package consistency checking) + */ + public DirectClassFile(ByteArray bytes, String filePath, + boolean strictParse) { + if (bytes == null) { + throw new NullPointerException("bytes == null"); + } + + if (filePath == null) { + throw new NullPointerException("filePath == null"); + } + + this.filePath = filePath; + this.bytes = bytes; + this.strictParse = strictParse; + this.accessFlags = -1; + } + + /** + * Constructs an instance. + * + * @param bytes {@code non-null;} the bytes of the file + * @param filePath {@code non-null;} the file path for the class, + * excluding any base directory specification + * @param strictParse whether to be strict about parsing; if + * {@code false}, this avoids doing checks that only exist + * for purposes of verification (such as magic number matching and + * path-package consistency checking) + */ + public DirectClassFile(byte[] bytes, String filePath, + boolean strictParse) { + this(new ByteArray(bytes), filePath, strictParse); + } + + /** + * Sets the parse observer for this instance. + * + * @param observer {@code null-ok;} the observer + */ + public void setObserver(ParseObserver observer) { + this.observer = observer; + } + + /** + * Sets the attribute factory to use. + * + * @param attributeFactory {@code non-null;} the attribute factory + */ + public void setAttributeFactory(AttributeFactory attributeFactory) { + if (attributeFactory == null) { + throw new NullPointerException("attributeFactory == null"); + } + + this.attributeFactory = attributeFactory; + } + + /** + * Gets the path where this class file is located. + * + * @return {@code non-null;} the filePath + */ + public String getFilePath() { + return filePath; + } + + /** + * Gets the {@link ByteArray} that this instance's data comes from. + * + * @return {@code non-null;} the bytes + */ + public ByteArray getBytes() { + return bytes; + } + + /** {@inheritDoc} */ + public int getMagic() { + parseToInterfacesIfNecessary(); + return getMagic0(); + } + + /** {@inheritDoc} */ + public int getMinorVersion() { + parseToInterfacesIfNecessary(); + return getMinorVersion0(); + } + + /** {@inheritDoc} */ + public int getMajorVersion() { + parseToInterfacesIfNecessary(); + return getMajorVersion0(); + } + + /** {@inheritDoc} */ + public int getAccessFlags() { + parseToInterfacesIfNecessary(); + return accessFlags; + } + + /** {@inheritDoc} */ + public CstType getThisClass() { + parseToInterfacesIfNecessary(); + return thisClass; + } + + /** {@inheritDoc} */ + public CstType getSuperclass() { + parseToInterfacesIfNecessary(); + return superClass; + } + + /** {@inheritDoc} */ + public ConstantPool getConstantPool() { + parseToInterfacesIfNecessary(); + return pool; + } + + /** {@inheritDoc} */ + public TypeList getInterfaces() { + parseToInterfacesIfNecessary(); + return interfaces; + } + + /** {@inheritDoc} */ + public FieldList getFields() { + parseToEndIfNecessary(); + return fields; + } + + /** {@inheritDoc} */ + public MethodList getMethods() { + parseToEndIfNecessary(); + return methods; + } + + /** {@inheritDoc} */ + public AttributeList getAttributes() { + parseToEndIfNecessary(); + return attributes; + } + + /** {@inheritDoc} */ + public CstString getSourceFile() { + AttributeList attribs = getAttributes(); + Attribute attSf = attribs.findFirst(AttSourceFile.ATTRIBUTE_NAME); + + if (attSf instanceof AttSourceFile) { + return ((AttSourceFile) attSf).getSourceFile(); + } + + return null; + } + + /** + * Constructs and returns an instance of {@link TypeList} whose + * data comes from the bytes of this instance, interpreted as a + * list of constant pool indices for classes, which are in turn + * translated to type constants. Instance construction will fail + * if any of the (alleged) indices turn out not to refer to + * constant pool entries of type {@code Class}. + * + * @param offset offset into {@link #bytes} for the start of the + * data + * @param size number of elements in the list (not number of bytes) + * @return {@code non-null;} an appropriately-constructed class list + */ + public TypeList makeTypeList(int offset, int size) { + if (size == 0) { + return StdTypeList.EMPTY; + } + + if (pool == null) { + throw new IllegalStateException("pool not yet initialized"); + } + + return new DcfTypeList(bytes, offset, size, pool, observer); + } + + /** + * Gets the class file field {@code magic}, but without doing any + * checks or parsing first. + * + * @return the magic value + */ + public int getMagic0() { + return bytes.getInt(0); + } + + /** + * Gets the class file field {@code minor_version}, but + * without doing any checks or parsing first. + * + * @return the minor version + */ + public int getMinorVersion0() { + return bytes.getUnsignedShort(4); + } + + /** + * Gets the class file field {@code major_version}, but + * without doing any checks or parsing first. + * + * @return the major version + */ + public int getMajorVersion0() { + return bytes.getUnsignedShort(6); + } + + /** + * Runs {@link #parse} if it has not yet been run to cover up to + * the interfaces list. + */ + private void parseToInterfacesIfNecessary() { + if (accessFlags == -1) { + parse(); + } + } + + /** + * Runs {@link #parse} if it has not yet been run successfully. + */ + private void parseToEndIfNecessary() { + if (attributes == null) { + parse(); + } + } + + /** + * Does the parsing, handing exceptions. + */ + private void parse() { + try { + parse0(); + } catch (ParseException ex) { + ex.addContext("...while parsing " + filePath); + throw ex; + } catch (RuntimeException ex) { + ParseException pe = new ParseException(ex); + pe.addContext("...while parsing " + filePath); + throw pe; + } + } + + /** + * Sees if the .class file header magic has the good value. + * + * @param magic the value of a classfile "magic" field + * @return true if the magic is valid + */ + private boolean isGoodMagic(int magic) { + return magic == CLASS_FILE_MAGIC; + } + + /** + * Sees if the .class file header version are within + * range. + * + * @param minorVersion the value of a classfile "minor_version" field + * @param majorVersion the value of a classfile "major_version" field + * @return true if the parameters are valid and within range + */ + private boolean isGoodVersion(int minorVersion, int majorVersion) { + /* Valid version ranges are typically of the form + * "A.0 through B.C inclusive" where A <= B and C >= 0, + * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION. + */ + if (minorVersion >= 0) { + /* Check against max first to handle the case where + * MIN_MAJOR == MAX_MAJOR. + */ + if (majorVersion == CLASS_FILE_MAX_MAJOR_VERSION) { + if (minorVersion <= CLASS_FILE_MAX_MINOR_VERSION) { + return true; + } + } else if (majorVersion < CLASS_FILE_MAX_MAJOR_VERSION && + majorVersion >= CLASS_FILE_MIN_MAJOR_VERSION) { + return true; + } + } + + return false; + } + + /** + * Does the actual parsing. + */ + private void parse0() { + if (bytes.size() < 10) { + throw new ParseException("severely truncated class file"); + } + + if (observer != null) { + observer.parsed(bytes, 0, 0, "begin classfile"); + observer.parsed(bytes, 0, 4, "magic: " + Hex.u4(getMagic0())); + observer.parsed(bytes, 4, 2, + "minor_version: " + Hex.u2(getMinorVersion0())); + observer.parsed(bytes, 6, 2, + "major_version: " + Hex.u2(getMajorVersion0())); + } + + if (strictParse) { + /* Make sure that this looks like a valid class file with a + * version that we can handle. + */ + if (!isGoodMagic(getMagic0())) { + throw new ParseException("bad class file magic (" + Hex.u4(getMagic0()) + ")"); + } + + if (!isGoodVersion(getMinorVersion0(), getMajorVersion0())) { + throw new ParseException("unsupported class file version " + + getMajorVersion0() + "." + + getMinorVersion0()); + } + } + + ConstantPoolParser cpParser = new ConstantPoolParser(bytes); + cpParser.setObserver(observer); + pool = cpParser.getPool(); + pool.setImmutable(); + + int at = cpParser.getEndOffset(); + int accessFlags = bytes.getUnsignedShort(at); // u2 access_flags; + int cpi = bytes.getUnsignedShort(at + 2); // u2 this_class; + thisClass = (CstType) pool.get(cpi); + cpi = bytes.getUnsignedShort(at + 4); // u2 super_class; + superClass = (CstType) pool.get0Ok(cpi); + int count = bytes.getUnsignedShort(at + 6); // u2 interfaces_count + + if (observer != null) { + observer.parsed(bytes, at, 2, + "access_flags: " + + AccessFlags.classString(accessFlags)); + observer.parsed(bytes, at + 2, 2, "this_class: " + thisClass); + observer.parsed(bytes, at + 4, 2, "super_class: " + + stringOrNone(superClass)); + observer.parsed(bytes, at + 6, 2, + "interfaces_count: " + Hex.u2(count)); + if (count != 0) { + observer.parsed(bytes, at + 8, 0, "interfaces:"); + } + } + + at += 8; + interfaces = makeTypeList(at, count); + at += count * 2; + + if (strictParse) { + /* + * Make sure that the file/jar path matches the declared + * package/class name. + */ + String thisClassName = thisClass.getClassType().getClassName(); + if (!(filePath.endsWith(".class") && + filePath.startsWith(thisClassName) && + (filePath.length() == (thisClassName.length() + 6)))) { + throw new ParseException("class name (" + thisClassName + + ") does not match path (" + + filePath + ")"); + } + } + + /* + * Only set the instance variable accessFlags here, since + * that's what signals a successful parse of the first part of + * the file (through the interfaces list). + */ + this.accessFlags = accessFlags; + + FieldListParser flParser = + new FieldListParser(this, thisClass, at, attributeFactory); + flParser.setObserver(observer); + fields = flParser.getList(); + at = flParser.getEndOffset(); + + MethodListParser mlParser = + new MethodListParser(this, thisClass, at, attributeFactory); + mlParser.setObserver(observer); + methods = mlParser.getList(); + at = mlParser.getEndOffset(); + + AttributeListParser alParser = + new AttributeListParser(this, AttributeFactory.CTX_CLASS, at, + attributeFactory); + alParser.setObserver(observer); + attributes = alParser.getList(); + attributes.setImmutable(); + at = alParser.getEndOffset(); + + if (at != bytes.size()) { + throw new ParseException("extra bytes at end of class file, " + + "at offset " + Hex.u4(at)); + } + + if (observer != null) { + observer.parsed(bytes, at, 0, "end classfile"); + } + } + + /** + * Implementation of {@link TypeList} whose data comes directly + * from the bytes of an instance of this (outer) class, + * interpreted as a list of constant pool indices for classes + * which are in turn returned as type constants. Instance + * construction will fail if any of the (alleged) indices turn out + * not to refer to constant pool entries of type + * {@code Class}. + */ + private static class DcfTypeList implements TypeList { + /** {@code non-null;} array containing the data */ + private final ByteArray bytes; + + /** number of elements in the list (not number of bytes) */ + private final int size; + + /** {@code non-null;} the constant pool */ + private final StdConstantPool pool; + + /** + * Constructs an instance. + * + * @param bytes {@code non-null;} original classfile's bytes + * @param offset offset into {@link #bytes} for the start of the + * data + * @param size number of elements in the list (not number of bytes) + * @param pool {@code non-null;} the constant pool to use + * @param observer {@code null-ok;} parse observer to use, if any + */ + public DcfTypeList(ByteArray bytes, int offset, int size, + StdConstantPool pool, ParseObserver observer) { + if (size < 0) { + throw new IllegalArgumentException("size < 0"); + } + + bytes = bytes.slice(offset, offset + size * 2); + this.bytes = bytes; + this.size = size; + this.pool = pool; + + for (int i = 0; i < size; i++) { + offset = i * 2; + int idx = bytes.getUnsignedShort(offset); + CstType type; + try { + type = (CstType) pool.get(idx); + } catch (ClassCastException ex) { + // Translate the exception. + throw new RuntimeException("bogus class cpi", ex); + } + if (observer != null) { + observer.parsed(bytes, offset, 2, " " + type); + } + } + } + + /** {@inheritDoc} */ + public boolean isMutable() { + return false; + } + + /** {@inheritDoc} */ + public int size() { + return size; + } + + /** {@inheritDoc} */ + public int getWordCount() { + // It is the same as size because all elements are classes. + return size; + } + + /** {@inheritDoc} */ + public Type getType(int n) { + int idx = bytes.getUnsignedShort(n * 2); + return ((CstType) pool.get(idx)).getClassType(); + } + + /** {@inheritDoc} */ + public TypeList withAddedType(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/FieldListParser.java b/dexlib/src/main/java/com/android/dx/cf/direct/FieldListParser.java new file mode 100644 index 000000000..2d8280ddc --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/FieldListParser.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dx.cf.iface.AttributeList; +import com.android.dx.cf.iface.Member; +import com.android.dx.cf.iface.StdField; +import com.android.dx.cf.iface.StdFieldList; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstType; + +/** + * Parser for lists of fields in a class file. + */ +final /*package*/ class FieldListParser extends MemberListParser { + /** {@code non-null;} list in progress */ + private final StdFieldList fields; + + /** + * Constructs an instance. + * + * @param cf {@code non-null;} the class file to parse from + * @param definer {@code non-null;} class being defined + * @param offset offset in {@code bytes} to the start of the list + * @param attributeFactory {@code non-null;} attribute factory to use + */ + public FieldListParser(DirectClassFile cf, CstType definer, int offset, + AttributeFactory attributeFactory) { + super(cf, definer, offset, attributeFactory); + fields = new StdFieldList(getCount()); + } + + /** + * Gets the parsed list. + * + * @return {@code non-null;} the parsed list + */ + public StdFieldList getList() { + parseIfNecessary(); + return fields; + } + + /** {@inheritDoc} */ + @Override + protected String humanName() { + return "field"; + } + + /** {@inheritDoc} */ + @Override + protected String humanAccessFlags(int accessFlags) { + return AccessFlags.fieldString(accessFlags); + } + + /** {@inheritDoc} */ + @Override + protected int getAttributeContext() { + return AttributeFactory.CTX_FIELD; + } + + /** {@inheritDoc} */ + @Override + protected Member set(int n, int accessFlags, CstNat nat, + AttributeList attributes) { + StdField field = + new StdField(getDefiner(), accessFlags, nat, attributes); + + fields.set(n, field); + return field; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/MemberListParser.java b/dexlib/src/main/java/com/android/dx/cf/direct/MemberListParser.java new file mode 100644 index 000000000..605bab896 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/MemberListParser.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dx.cf.iface.AttributeList; +import com.android.dx.cf.iface.Member; +import com.android.dx.cf.iface.ParseException; +import com.android.dx.cf.iface.ParseObserver; +import com.android.dx.cf.iface.StdAttributeList; +import com.android.dx.rop.cst.ConstantPool; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; + +/** + * Parser for lists of class file members (that is, fields and methods). + */ +abstract /*package*/ class MemberListParser { + /** {@code non-null;} the class file to parse from */ + private final DirectClassFile cf; + + /** {@code non-null;} class being defined */ + private final CstType definer; + + /** offset in the byte array of the classfile to the start of the list */ + private final int offset; + + /** {@code non-null;} attribute factory to use */ + private final AttributeFactory attributeFactory; + + /** {@code >= -1;} the end offset of this list in the byte array of the + * classfile, or {@code -1} if not yet parsed */ + private int endOffset; + + /** {@code null-ok;} parse observer, if any */ + private ParseObserver observer; + + /** + * Constructs an instance. + * + * @param cf {@code non-null;} the class file to parse from + * @param definer {@code non-null;} class being defined + * @param offset offset in {@code bytes} to the start of the list + * @param attributeFactory {@code non-null;} attribute factory to use + */ + public MemberListParser(DirectClassFile cf, CstType definer, + int offset, AttributeFactory attributeFactory) { + if (cf == null) { + throw new NullPointerException("cf == null"); + } + + if (offset < 0) { + throw new IllegalArgumentException("offset < 0"); + } + + if (attributeFactory == null) { + throw new NullPointerException("attributeFactory == null"); + } + + this.cf = cf; + this.definer = definer; + this.offset = offset; + this.attributeFactory = attributeFactory; + this.endOffset = -1; + } + + /** + * Gets the end offset of this constant pool in the {@code byte[]} + * which it came from. + * + * @return {@code >= 0;} the end offset + */ + public int getEndOffset() { + parseIfNecessary(); + return endOffset; + } + + /** + * Sets the parse observer for this instance. + * + * @param observer {@code null-ok;} the observer + */ + public final void setObserver(ParseObserver observer) { + this.observer = observer; + } + + /** + * Runs {@link #parse} if it has not yet been run successfully. + */ + protected final void parseIfNecessary() { + if (endOffset < 0) { + parse(); + } + } + + /** + * Gets the count of elements in the list. + * + * @return the count + */ + protected final int getCount() { + ByteArray bytes = cf.getBytes(); + return bytes.getUnsignedShort(offset); + } + + /** + * Gets the class file being defined. + * + * @return {@code non-null;} the class + */ + protected final CstType getDefiner() { + return definer; + } + + /** + * Gets the human-oriented name for what this instance is parsing. + * Subclasses must override this method. + * + * @return {@code non-null;} the human oriented name + */ + protected abstract String humanName(); + + /** + * Gets the human-oriented string for the given access flags. + * Subclasses must override this method. + * + * @param accessFlags the flags + * @return {@code non-null;} the string form + */ + protected abstract String humanAccessFlags(int accessFlags); + + /** + * Gets the {@code CTX_*} constant to use when parsing attributes. + * Subclasses must override this method. + * + * @return {@code non-null;} the human oriented name + */ + protected abstract int getAttributeContext(); + + /** + * Sets an element in the list. Subclasses must override this method. + * + * @param n which element + * @param accessFlags the {@code access_flags} + * @param nat the interpreted name and type (based on the two + * {@code *_index} fields) + * @param attributes list of parsed attributes + * @return {@code non-null;} the constructed member + */ + protected abstract Member set(int n, int accessFlags, CstNat nat, + AttributeList attributes); + + /** + * Does the actual parsing. + */ + private void parse() { + int attributeContext = getAttributeContext(); + int count = getCount(); + int at = offset + 2; // Skip the count. + + ByteArray bytes = cf.getBytes(); + ConstantPool pool = cf.getConstantPool(); + + if (observer != null) { + observer.parsed(bytes, offset, 2, + humanName() + "s_count: " + Hex.u2(count)); + } + + for (int i = 0; i < count; i++) { + try { + int accessFlags = bytes.getUnsignedShort(at); + int nameIdx = bytes.getUnsignedShort(at + 2); + int descIdx = bytes.getUnsignedShort(at + 4); + CstString name = (CstString) pool.get(nameIdx); + CstString desc = (CstString) pool.get(descIdx); + + if (observer != null) { + observer.startParsingMember(bytes, at, name.getString(), + desc.getString()); + observer.parsed(bytes, at, 0, "\n" + humanName() + + "s[" + i + "]:\n"); + observer.changeIndent(1); + observer.parsed(bytes, at, 2, + "access_flags: " + + humanAccessFlags(accessFlags)); + observer.parsed(bytes, at + 2, 2, + "name: " + name.toHuman()); + observer.parsed(bytes, at + 4, 2, + "descriptor: " + desc.toHuman()); + } + + at += 6; + AttributeListParser parser = + new AttributeListParser(cf, attributeContext, at, + attributeFactory); + parser.setObserver(observer); + at = parser.getEndOffset(); + StdAttributeList attributes = parser.getList(); + attributes.setImmutable(); + CstNat nat = new CstNat(name, desc); + Member member = set(i, accessFlags, nat, attributes); + + if (observer != null) { + observer.changeIndent(-1); + observer.parsed(bytes, at, 0, "end " + humanName() + + "s[" + i + "]\n"); + observer.endParsingMember(bytes, at, name.getString(), + desc.getString(), member); + } + } catch (ParseException ex) { + ex.addContext("...while parsing " + humanName() + "s[" + i + + "]"); + throw ex; + } catch (RuntimeException ex) { + ParseException pe = new ParseException(ex); + pe.addContext("...while parsing " + humanName() + "s[" + i + + "]"); + throw pe; + } + } + + endOffset = at; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/MethodListParser.java b/dexlib/src/main/java/com/android/dx/cf/direct/MethodListParser.java new file mode 100644 index 000000000..9e3494e67 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/MethodListParser.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dx.cf.iface.AttributeList; +import com.android.dx.cf.iface.Member; +import com.android.dx.cf.iface.StdMethod; +import com.android.dx.cf.iface.StdMethodList; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstType; + +/** + * Parser for lists of methods in a class file. + */ +final /*package*/ class MethodListParser extends MemberListParser { + /** {@code non-null;} list in progress */ + final private StdMethodList methods; + + /** + * Constructs an instance. + * + * @param cf {@code non-null;} the class file to parse from + * @param definer {@code non-null;} class being defined + * @param offset offset in {@code bytes} to the start of the list + * @param attributeFactory {@code non-null;} attribute factory to use + */ + public MethodListParser(DirectClassFile cf, CstType definer, + int offset, AttributeFactory attributeFactory) { + super(cf, definer, offset, attributeFactory); + methods = new StdMethodList(getCount()); + } + + /** + * Gets the parsed list. + * + * @return {@code non-null;} the parsed list + */ + public StdMethodList getList() { + parseIfNecessary(); + return methods; + } + + /** {@inheritDoc} */ + @Override + protected String humanName() { + return "method"; + } + + /** {@inheritDoc} */ + @Override + protected String humanAccessFlags(int accessFlags) { + return AccessFlags.methodString(accessFlags); + } + + /** {@inheritDoc} */ + @Override + protected int getAttributeContext() { + return AttributeFactory.CTX_METHOD; + } + + /** {@inheritDoc} */ + @Override + protected Member set(int n, int accessFlags, CstNat nat, + AttributeList attributes) { + StdMethod meth = + new StdMethod(getDefiner(), accessFlags, nat, attributes); + + methods.set(n, meth); + return meth; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/StdAttributeFactory.java b/dexlib/src/main/java/com/android/dx/cf/direct/StdAttributeFactory.java new file mode 100644 index 000000000..710ba5763 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/StdAttributeFactory.java @@ -0,0 +1,783 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.direct; + +import com.android.dx.cf.attrib.AttAnnotationDefault; +import com.android.dx.cf.attrib.AttCode; +import com.android.dx.cf.attrib.AttConstantValue; +import com.android.dx.cf.attrib.AttDeprecated; +import com.android.dx.cf.attrib.AttEnclosingMethod; +import com.android.dx.cf.attrib.AttExceptions; +import com.android.dx.cf.attrib.AttInnerClasses; +import com.android.dx.cf.attrib.AttLineNumberTable; +import com.android.dx.cf.attrib.AttLocalVariableTable; +import com.android.dx.cf.attrib.AttLocalVariableTypeTable; +import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations; +import com.android.dx.cf.attrib.AttRuntimeInvisibleParameterAnnotations; +import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations; +import com.android.dx.cf.attrib.AttRuntimeVisibleParameterAnnotations; +import com.android.dx.cf.attrib.AttSignature; +import com.android.dx.cf.attrib.AttSourceDebugExtension; +import com.android.dx.cf.attrib.AttSourceFile; +import com.android.dx.cf.attrib.AttSynthetic; +import com.android.dx.cf.attrib.InnerClassList; +import com.android.dx.cf.code.ByteCatchList; +import com.android.dx.cf.code.BytecodeArray; +import com.android.dx.cf.code.LineNumberList; +import com.android.dx.cf.code.LocalVariableList; +import com.android.dx.cf.iface.Attribute; +import com.android.dx.cf.iface.ParseException; +import com.android.dx.cf.iface.ParseObserver; +import com.android.dx.cf.iface.StdAttributeList; +import com.android.dx.rop.annotation.AnnotationVisibility; +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.ConstantPool; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.cst.TypedConstant; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; +import java.io.IOException; + +/** + * Standard subclass of {@link AttributeFactory}, which knows how to parse + * all the standard attribute types. + */ +public class StdAttributeFactory + extends AttributeFactory { + /** {@code non-null;} shared instance of this class */ + public static final StdAttributeFactory THE_ONE = + new StdAttributeFactory(); + + /** + * Constructs an instance. + */ + public StdAttributeFactory() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + protected Attribute parse0(DirectClassFile cf, int context, String name, + int offset, int length, ParseObserver observer) { + switch (context) { + case CTX_CLASS: { + if (name == AttDeprecated.ATTRIBUTE_NAME) { + return deprecated(cf, offset, length, observer); + } + if (name == AttEnclosingMethod.ATTRIBUTE_NAME) { + return enclosingMethod(cf, offset, length, observer); + } + if (name == AttInnerClasses.ATTRIBUTE_NAME) { + return innerClasses(cf, offset, length, observer); + } + if (name == AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME) { + return runtimeInvisibleAnnotations(cf, offset, length, + observer); + } + if (name == AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME) { + return runtimeVisibleAnnotations(cf, offset, length, + observer); + } + if (name == AttSynthetic.ATTRIBUTE_NAME) { + return synthetic(cf, offset, length, observer); + } + if (name == AttSignature.ATTRIBUTE_NAME) { + return signature(cf, offset, length, observer); + } + if (name == AttSourceDebugExtension.ATTRIBUTE_NAME) { + return sourceDebugExtension(cf, offset, length, observer); + } + if (name == AttSourceFile.ATTRIBUTE_NAME) { + return sourceFile(cf, offset, length, observer); + } + break; + } + case CTX_FIELD: { + if (name == AttConstantValue.ATTRIBUTE_NAME) { + return constantValue(cf, offset, length, observer); + } + if (name == AttDeprecated.ATTRIBUTE_NAME) { + return deprecated(cf, offset, length, observer); + } + if (name == AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME) { + return runtimeInvisibleAnnotations(cf, offset, length, + observer); + } + if (name == AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME) { + return runtimeVisibleAnnotations(cf, offset, length, + observer); + } + if (name == AttSignature.ATTRIBUTE_NAME) { + return signature(cf, offset, length, observer); + } + if (name == AttSynthetic.ATTRIBUTE_NAME) { + return synthetic(cf, offset, length, observer); + } + break; + } + case CTX_METHOD: { + if (name == AttAnnotationDefault.ATTRIBUTE_NAME) { + return annotationDefault(cf, offset, length, observer); + } + if (name == AttCode.ATTRIBUTE_NAME) { + return code(cf, offset, length, observer); + } + if (name == AttDeprecated.ATTRIBUTE_NAME) { + return deprecated(cf, offset, length, observer); + } + if (name == AttExceptions.ATTRIBUTE_NAME) { + return exceptions(cf, offset, length, observer); + } + if (name == AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME) { + return runtimeInvisibleAnnotations(cf, offset, length, + observer); + } + if (name == AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME) { + return runtimeVisibleAnnotations(cf, offset, length, + observer); + } + if (name == AttRuntimeInvisibleParameterAnnotations. + ATTRIBUTE_NAME) { + return runtimeInvisibleParameterAnnotations( + cf, offset, length, observer); + } + if (name == AttRuntimeVisibleParameterAnnotations. + ATTRIBUTE_NAME) { + return runtimeVisibleParameterAnnotations( + cf, offset, length, observer); + } + if (name == AttSignature.ATTRIBUTE_NAME) { + return signature(cf, offset, length, observer); + } + if (name == AttSynthetic.ATTRIBUTE_NAME) { + return synthetic(cf, offset, length, observer); + } + break; + } + case CTX_CODE: { + if (name == AttLineNumberTable.ATTRIBUTE_NAME) { + return lineNumberTable(cf, offset, length, observer); + } + if (name == AttLocalVariableTable.ATTRIBUTE_NAME) { + return localVariableTable(cf, offset, length, observer); + } + if (name == AttLocalVariableTypeTable.ATTRIBUTE_NAME) { + return localVariableTypeTable(cf, offset, length, + observer); + } + break; + } + } + + return super.parse0(cf, context, name, offset, length, observer); + } + + /** + * Parses an {@code AnnotationDefault} attribute. + */ + private Attribute annotationDefault(DirectClassFile cf, + int offset, int length, ParseObserver observer) { + if (length < 2) { + throwSeverelyTruncated(); + } + + AnnotationParser ap = + new AnnotationParser(cf, offset, length, observer); + Constant cst = ap.parseValueAttribute(); + + return new AttAnnotationDefault(cst, length); + } + + /** + * Parses a {@code Code} attribute. + */ + private Attribute code(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + if (length < 12) { + return throwSeverelyTruncated(); + } + + ByteArray bytes = cf.getBytes(); + ConstantPool pool = cf.getConstantPool(); + int maxStack = bytes.getUnsignedShort(offset); // u2 max_stack + int maxLocals = bytes.getUnsignedShort(offset + 2); // u2 max_locals + int codeLength = bytes.getInt(offset + 4); // u4 code_length + int origOffset = offset; + + if (observer != null) { + observer.parsed(bytes, offset, 2, + "max_stack: " + Hex.u2(maxStack)); + observer.parsed(bytes, offset + 2, 2, + "max_locals: " + Hex.u2(maxLocals)); + observer.parsed(bytes, offset + 4, 4, + "code_length: " + Hex.u4(codeLength)); + } + + offset += 8; + length -= 8; + + if (length < (codeLength + 4)) { + return throwTruncated(); + } + + int codeOffset = offset; + offset += codeLength; + length -= codeLength; + BytecodeArray code = + new BytecodeArray(bytes.slice(codeOffset, codeOffset + codeLength), + pool); + if (observer != null) { + code.forEach(new CodeObserver(code.getBytes(), observer)); + } + + // u2 exception_table_length + int exceptionTableLength = bytes.getUnsignedShort(offset); + ByteCatchList catches = (exceptionTableLength == 0) ? + ByteCatchList.EMPTY : + new ByteCatchList(exceptionTableLength); + + if (observer != null) { + observer.parsed(bytes, offset, 2, + "exception_table_length: " + + Hex.u2(exceptionTableLength)); + } + + offset += 2; + length -= 2; + + if (length < (exceptionTableLength * 8 + 2)) { + return throwTruncated(); + } + + for (int i = 0; i < exceptionTableLength; i++) { + if (observer != null) { + observer.changeIndent(1); + } + + int startPc = bytes.getUnsignedShort(offset); + int endPc = bytes.getUnsignedShort(offset + 2); + int handlerPc = bytes.getUnsignedShort(offset + 4); + int catchTypeIdx = bytes.getUnsignedShort(offset + 6); + CstType catchType = (CstType) pool.get0Ok(catchTypeIdx); + catches.set(i, startPc, endPc, handlerPc, catchType); + if (observer != null) { + observer.parsed(bytes, offset, 8, + Hex.u2(startPc) + ".." + Hex.u2(endPc) + + " -> " + Hex.u2(handlerPc) + " " + + ((catchType == null) ? "" : + catchType.toHuman())); + } + offset += 8; + length -= 8; + + if (observer != null) { + observer.changeIndent(-1); + } + } + + catches.setImmutable(); + + AttributeListParser parser = + new AttributeListParser(cf, CTX_CODE, offset, this); + parser.setObserver(observer); + + StdAttributeList attributes = parser.getList(); + attributes.setImmutable(); + + int attributeByteCount = parser.getEndOffset() - offset; + if (attributeByteCount != length) { + return throwBadLength(attributeByteCount + (offset - origOffset)); + } + + return new AttCode(maxStack, maxLocals, code, catches, attributes); + } + + /** + * Parses a {@code ConstantValue} attribute. + */ + private Attribute constantValue(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + if (length != 2) { + return throwBadLength(2); + } + + ByteArray bytes = cf.getBytes(); + ConstantPool pool = cf.getConstantPool(); + int idx = bytes.getUnsignedShort(offset); + TypedConstant cst = (TypedConstant) pool.get(idx); + Attribute result = new AttConstantValue(cst); + + if (observer != null) { + observer.parsed(bytes, offset, 2, "value: " + cst); + } + + return result; + } + + /** + * Parses a {@code Deprecated} attribute. + */ + private Attribute deprecated(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + if (length != 0) { + return throwBadLength(0); + } + + return new AttDeprecated(); + } + + /** + * Parses an {@code EnclosingMethod} attribute. + */ + private Attribute enclosingMethod(DirectClassFile cf, int offset, + int length, ParseObserver observer) { + if (length != 4) { + throwBadLength(4); + } + + ByteArray bytes = cf.getBytes(); + ConstantPool pool = cf.getConstantPool(); + + int idx = bytes.getUnsignedShort(offset); + CstType type = (CstType) pool.get(idx); + + idx = bytes.getUnsignedShort(offset + 2); + CstNat method = (CstNat) pool.get0Ok(idx); + + Attribute result = new AttEnclosingMethod(type, method); + + if (observer != null) { + observer.parsed(bytes, offset, 2, "class: " + type); + observer.parsed(bytes, offset + 2, 2, "method: " + + DirectClassFile.stringOrNone(method)); + } + + return result; + } + + /** + * Parses an {@code Exceptions} attribute. + */ + private Attribute exceptions(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + if (length < 2) { + return throwSeverelyTruncated(); + } + + ByteArray bytes = cf.getBytes(); + int count = bytes.getUnsignedShort(offset); // number_of_exceptions + + if (observer != null) { + observer.parsed(bytes, offset, 2, + "number_of_exceptions: " + Hex.u2(count)); + } + + offset += 2; + length -= 2; + + if (length != (count * 2)) { + throwBadLength((count * 2) + 2); + } + + TypeList list = cf.makeTypeList(offset, count); + return new AttExceptions(list); + } + + /** + * Parses an {@code InnerClasses} attribute. + */ + private Attribute innerClasses(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + if (length < 2) { + return throwSeverelyTruncated(); + } + + ByteArray bytes = cf.getBytes(); + ConstantPool pool = cf.getConstantPool(); + int count = bytes.getUnsignedShort(offset); // number_of_classes + + if (observer != null) { + observer.parsed(bytes, offset, 2, + "number_of_classes: " + Hex.u2(count)); + } + + offset += 2; + length -= 2; + + if (length != (count * 8)) { + throwBadLength((count * 8) + 2); + } + + InnerClassList list = new InnerClassList(count); + + for (int i = 0; i < count; i++) { + int innerClassIdx = bytes.getUnsignedShort(offset); + int outerClassIdx = bytes.getUnsignedShort(offset + 2); + int nameIdx = bytes.getUnsignedShort(offset + 4); + int accessFlags = bytes.getUnsignedShort(offset + 6); + CstType innerClass = (CstType) pool.get(innerClassIdx); + CstType outerClass = (CstType) pool.get0Ok(outerClassIdx); + CstString name = (CstString) pool.get0Ok(nameIdx); + list.set(i, innerClass, outerClass, name, accessFlags); + if (observer != null) { + observer.parsed(bytes, offset, 2, + "inner_class: " + + DirectClassFile.stringOrNone(innerClass)); + observer.parsed(bytes, offset + 2, 2, + " outer_class: " + + DirectClassFile.stringOrNone(outerClass)); + observer.parsed(bytes, offset + 4, 2, + " name: " + + DirectClassFile.stringOrNone(name)); + observer.parsed(bytes, offset + 6, 2, + " access_flags: " + + AccessFlags.innerClassString(accessFlags)); + } + offset += 8; + } + + list.setImmutable(); + return new AttInnerClasses(list); + } + + /** + * Parses a {@code LineNumberTable} attribute. + */ + private Attribute lineNumberTable(DirectClassFile cf, int offset, + int length, ParseObserver observer) { + if (length < 2) { + return throwSeverelyTruncated(); + } + + ByteArray bytes = cf.getBytes(); + int count = bytes.getUnsignedShort(offset); // line_number_table_length + + if (observer != null) { + observer.parsed(bytes, offset, 2, + "line_number_table_length: " + Hex.u2(count)); + } + + offset += 2; + length -= 2; + + if (length != (count * 4)) { + throwBadLength((count * 4) + 2); + } + + LineNumberList list = new LineNumberList(count); + + for (int i = 0; i < count; i++) { + int startPc = bytes.getUnsignedShort(offset); + int lineNumber = bytes.getUnsignedShort(offset + 2); + list.set(i, startPc, lineNumber); + if (observer != null) { + observer.parsed(bytes, offset, 4, + Hex.u2(startPc) + " " + lineNumber); + } + offset += 4; + } + + list.setImmutable(); + return new AttLineNumberTable(list); + } + + /** + * Parses a {@code LocalVariableTable} attribute. + */ + private Attribute localVariableTable(DirectClassFile cf, int offset, + int length, ParseObserver observer) { + if (length < 2) { + return throwSeverelyTruncated(); + } + + ByteArray bytes = cf.getBytes(); + int count = bytes.getUnsignedShort(offset); + + if (observer != null) { + observer.parsed(bytes, offset, 2, + "local_variable_table_length: " + Hex.u2(count)); + } + + LocalVariableList list = parseLocalVariables( + bytes.slice(offset + 2, offset + length), cf.getConstantPool(), + observer, count, false); + return new AttLocalVariableTable(list); + } + + /** + * Parses a {@code LocalVariableTypeTable} attribute. + */ + private Attribute localVariableTypeTable(DirectClassFile cf, int offset, + int length, ParseObserver observer) { + if (length < 2) { + return throwSeverelyTruncated(); + } + + ByteArray bytes = cf.getBytes(); + int count = bytes.getUnsignedShort(offset); + + if (observer != null) { + observer.parsed(bytes, offset, 2, + "local_variable_type_table_length: " + Hex.u2(count)); + } + + LocalVariableList list = parseLocalVariables( + bytes.slice(offset + 2, offset + length), cf.getConstantPool(), + observer, count, true); + return new AttLocalVariableTypeTable(list); + } + + /** + * Parse the table part of either a {@code LocalVariableTable} + * or a {@code LocalVariableTypeTable}. + * + * @param bytes {@code non-null;} bytes to parse, which should only + * contain the table data (no header) + * @param pool {@code non-null;} constant pool to use + * @param count {@code >= 0;} the number of entries + * @param typeTable {@code true} iff this is for a type table + * @return {@code non-null;} the constructed list + */ + private LocalVariableList parseLocalVariables(ByteArray bytes, + ConstantPool pool, ParseObserver observer, int count, + boolean typeTable) { + if (bytes.size() != (count * 10)) { + // "+ 2" is for the count. + throwBadLength((count * 10) + 2); + } + + ByteArray.MyDataInputStream in = bytes.makeDataInputStream(); + LocalVariableList list = new LocalVariableList(count); + + try { + for (int i = 0; i < count; i++) { + int startPc = in.readUnsignedShort(); + int length = in.readUnsignedShort(); + int nameIdx = in.readUnsignedShort(); + int typeIdx = in.readUnsignedShort(); + int index = in.readUnsignedShort(); + CstString name = (CstString) pool.get(nameIdx); + CstString type = (CstString) pool.get(typeIdx); + CstString descriptor = null; + CstString signature = null; + + if (typeTable) { + signature = type; + } else { + descriptor = type; + } + + list.set(i, startPc, length, name, + descriptor, signature, index); + + if (observer != null) { + observer.parsed(bytes, i * 10, 10, Hex.u2(startPc) + + ".." + Hex.u2(startPc + length) + " " + + Hex.u2(index) + " " + name.toHuman() + " " + + type.toHuman()); + } + } + } catch (IOException ex) { + throw new RuntimeException("shouldn't happen", ex); + } + + list.setImmutable(); + return list; + } + + /** + * Parses a {@code RuntimeInvisibleAnnotations} attribute. + */ + private Attribute runtimeInvisibleAnnotations(DirectClassFile cf, + int offset, int length, ParseObserver observer) { + if (length < 2) { + throwSeverelyTruncated(); + } + + AnnotationParser ap = + new AnnotationParser(cf, offset, length, observer); + Annotations annotations = + ap.parseAnnotationAttribute(AnnotationVisibility.BUILD); + + return new AttRuntimeInvisibleAnnotations(annotations, length); + } + + /** + * Parses a {@code RuntimeVisibleAnnotations} attribute. + */ + private Attribute runtimeVisibleAnnotations(DirectClassFile cf, + int offset, int length, ParseObserver observer) { + if (length < 2) { + throwSeverelyTruncated(); + } + + AnnotationParser ap = + new AnnotationParser(cf, offset, length, observer); + Annotations annotations = + ap.parseAnnotationAttribute(AnnotationVisibility.RUNTIME); + + return new AttRuntimeVisibleAnnotations(annotations, length); + } + + /** + * Parses a {@code RuntimeInvisibleParameterAnnotations} attribute. + */ + private Attribute runtimeInvisibleParameterAnnotations(DirectClassFile cf, + int offset, int length, ParseObserver observer) { + if (length < 2) { + throwSeverelyTruncated(); + } + + AnnotationParser ap = + new AnnotationParser(cf, offset, length, observer); + AnnotationsList list = + ap.parseParameterAttribute(AnnotationVisibility.BUILD); + + return new AttRuntimeInvisibleParameterAnnotations(list, length); + } + + /** + * Parses a {@code RuntimeVisibleParameterAnnotations} attribute. + */ + private Attribute runtimeVisibleParameterAnnotations(DirectClassFile cf, + int offset, int length, ParseObserver observer) { + if (length < 2) { + throwSeverelyTruncated(); + } + + AnnotationParser ap = + new AnnotationParser(cf, offset, length, observer); + AnnotationsList list = + ap.parseParameterAttribute(AnnotationVisibility.RUNTIME); + + return new AttRuntimeVisibleParameterAnnotations(list, length); + } + + /** + * Parses a {@code Signature} attribute. + */ + private Attribute signature(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + if (length != 2) { + throwBadLength(2); + } + + ByteArray bytes = cf.getBytes(); + ConstantPool pool = cf.getConstantPool(); + int idx = bytes.getUnsignedShort(offset); + CstString cst = (CstString) pool.get(idx); + Attribute result = new AttSignature(cst); + + if (observer != null) { + observer.parsed(bytes, offset, 2, "signature: " + cst); + } + + return result; + } + + /** + * Parses a {@code SourceDebugExtesion} attribute. + */ + private Attribute sourceDebugExtension(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + ByteArray bytes = cf.getBytes().slice(offset, offset + length); + CstString smapString = new CstString(bytes); + Attribute result = new AttSourceDebugExtension(smapString); + + if (observer != null) { + String decoded = smapString.getString(); + observer.parsed(bytes, offset, length, "sourceDebugExtension: " + decoded); + } + + return result; + } + + /** + * Parses a {@code SourceFile} attribute. + */ + private Attribute sourceFile(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + if (length != 2) { + throwBadLength(2); + } + + ByteArray bytes = cf.getBytes(); + ConstantPool pool = cf.getConstantPool(); + int idx = bytes.getUnsignedShort(offset); + CstString cst = (CstString) pool.get(idx); + Attribute result = new AttSourceFile(cst); + + if (observer != null) { + observer.parsed(bytes, offset, 2, "source: " + cst); + } + + return result; + } + + /** + * Parses a {@code Synthetic} attribute. + */ + private Attribute synthetic(DirectClassFile cf, int offset, int length, + ParseObserver observer) { + if (length != 0) { + return throwBadLength(0); + } + + return new AttSynthetic(); + } + + /** + * Throws the right exception when a known attribute has a way too short + * length. + * + * @return never + * @throws ParseException always thrown + */ + private static Attribute throwSeverelyTruncated() { + throw new ParseException("severely truncated attribute"); + } + + /** + * Throws the right exception when a known attribute has a too short + * length. + * + * @return never + * @throws ParseException always thrown + */ + private static Attribute throwTruncated() { + throw new ParseException("truncated attribute"); + } + + /** + * Throws the right exception when an attribute has an unexpected length + * (given its contents). + * + * @param expected expected length + * @return never + * @throws ParseException always thrown + */ + private static Attribute throwBadLength(int expected) { + throw new ParseException("bad attribute length; expected length " + + Hex.u4(expected)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/direct/package.html b/dexlib/src/main/java/com/android/dx/cf/direct/package.html new file mode 100644 index 000000000..2a4619843 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/direct/package.html @@ -0,0 +1,12 @@ + +

Implementation of cf.iface.* based on a direct representation +of class files as byte[]s.

+ +

PACKAGES USED: +

    +
  • com.android.dx.cf.attrib
  • +
  • com.android.dx.cf.iface
  • +
  • com.android.dx.rop.pool
  • +
  • com.android.dx.util
  • +
+ diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/Attribute.java b/dexlib/src/main/java/com/android/dx/cf/iface/Attribute.java new file mode 100644 index 000000000..b075251db --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/Attribute.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +/** + * Interface representing attributes of class files (directly or indirectly). + */ +public interface Attribute { + /** + * Get the name of the attribute. + * + * @return {@code non-null;} the name + */ + public String getName(); + + /** + * Get the total length of the attribute in bytes, including the + * header. Since the header is always six bytes, the result of + * this method is always at least {@code 6}. + * + * @return {@code >= 6;} the total length, in bytes + */ + public int byteLength(); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/AttributeList.java b/dexlib/src/main/java/com/android/dx/cf/iface/AttributeList.java new file mode 100644 index 000000000..f7a1d2705 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/AttributeList.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +/** + * Interface for lists of attributes. + */ +public interface AttributeList { + /** + * Get whether this instance is mutable. Note that the + * {@code AttributeList} interface itself doesn't provide any means + * of mutation, but that doesn't mean that there isn't a non-interface + * way of mutating an instance. + * + * @return {@code true} iff this instance is somehow mutable + */ + public boolean isMutable(); + + /** + * Get the number of attributes in the list. + * + * @return the size + */ + public int size(); + + /** + * Get the {@code n}th attribute. + * + * @param n {@code n >= 0, n < size();} which attribute + * @return {@code non-null;} the attribute in question + */ + public Attribute get(int n); + + /** + * Get the total length of this list in bytes, when part of a + * class file. The returned value includes the two bytes for the + * {@code attributes_count} length indicator. + * + * @return {@code >= 2;} the total length, in bytes + */ + public int byteLength(); + + /** + * Get the first attribute in the list with the given name, if any. + * + * @param name {@code non-null;} attribute name + * @return {@code null-ok;} first attribute in the list with the given name, + * or {@code null} if there is none + */ + public Attribute findFirst(String name); + + /** + * Get the next attribute in the list after the given one, with the same + * name, if any. + * + * @param attrib {@code non-null;} attribute to start looking after + * @return {@code null-ok;} next attribute after {@code attrib} with the + * same name as {@code attrib} + */ + public Attribute findNext(Attribute attrib); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/ClassFile.java b/dexlib/src/main/java/com/android/dx/cf/iface/ClassFile.java new file mode 100644 index 000000000..d6c9ed03e --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/ClassFile.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.rop.cst.ConstantPool; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.TypeList; + +/** + * Interface for things which purport to be class files or reasonable + * facsimiles thereof. + * + *

Note: The fields referred to in this documentation are of the + * {@code ClassFile} structure defined in vmspec-2 sec4.1. + */ +public interface ClassFile extends HasAttribute { + /** + * Gets the field {@code magic}. + * + * @return the value in question + */ + public int getMagic(); + + /** + * Gets the field {@code minor_version}. + * + * @return the value in question + */ + public int getMinorVersion(); + + /** + * Gets the field {@code major_version}. + * + * @return the value in question + */ + public int getMajorVersion(); + + /** + * Gets the field {@code access_flags}. + * + * @return the value in question + */ + public int getAccessFlags(); + + /** + * Gets the field {@code this_class}, interpreted as a type constant. + * + * @return {@code non-null;} the value in question + */ + public CstType getThisClass(); + + /** + * Gets the field {@code super_class}, interpreted as a type constant + * if non-zero. + * + * @return {@code null-ok;} the value in question + */ + public CstType getSuperclass(); + + /** + * Gets the field {@code constant_pool} (along with + * {@code constant_pool_count}). + * + * @return {@code non-null;} the constant pool + */ + public ConstantPool getConstantPool(); + + /** + * Gets the field {@code interfaces} (along with + * {@code interfaces_count}). + * + * @return {@code non-null;} the list of interfaces + */ + public TypeList getInterfaces(); + + /** + * Gets the field {@code fields} (along with + * {@code fields_count}). + * + * @return {@code non-null;} the list of fields + */ + public FieldList getFields(); + + /** + * Gets the field {@code methods} (along with + * {@code methods_count}). + * + * @return {@code non-null;} the list of fields + */ + public MethodList getMethods(); + + /** + * Gets the field {@code attributes} (along with + * {@code attributes_count}). + * + * @return {@code non-null;} the list of attributes + */ + public AttributeList getAttributes(); + + /** + * Gets the name out of the {@code SourceFile} attribute of this + * file, if any. This is a convenient shorthand for scrounging around + * the class's attributes. + * + * @return {@code non-null;} the constant pool + */ + public CstString getSourceFile(); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/Field.java b/dexlib/src/main/java/com/android/dx/cf/iface/Field.java new file mode 100644 index 000000000..e3002bcd0 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/Field.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.rop.cst.TypedConstant; + +/** + * Interface representing fields of class files. + */ +public interface Field + extends Member { + /** + * Get the constant value for this field, if any. This only returns + * non-{@code null} for a {@code static final} field which + * includes a {@code ConstantValue} attribute. + * + * @return {@code null-ok;} the constant value, or {@code null} if this + * field isn't a constant + */ + public TypedConstant getConstantValue(); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/FieldList.java b/dexlib/src/main/java/com/android/dx/cf/iface/FieldList.java new file mode 100644 index 000000000..9cd27a311 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/FieldList.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +/** + * Interface for lists of fields. + */ +public interface FieldList +{ + /** + * Get whether this instance is mutable. Note that the + * {@code FieldList} interface itself doesn't provide any means + * of mutation, but that doesn't mean that there isn't a non-interface + * way of mutating an instance. + * + * @return {@code true} iff this instance is somehow mutable + */ + public boolean isMutable(); + + /** + * Get the number of fields in the list. + * + * @return the size + */ + public int size(); + + /** + * Get the {@code n}th field. + * + * @param n {@code n >= 0, n < size();} which field + * @return {@code non-null;} the field in question + */ + public Field get(int n); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/HasAttribute.java b/dexlib/src/main/java/com/android/dx/cf/iface/HasAttribute.java new file mode 100644 index 000000000..9f3e48db2 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/HasAttribute.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +/** + * An element that can have {@link Attribute} + */ +public interface HasAttribute { + + /** + * Get the element {@code attributes} (along with + * {@code attributes_count}). + * + * @return {@code non-null;} the attributes list + */ + public AttributeList getAttributes(); + +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/Member.java b/dexlib/src/main/java/com/android/dx/cf/iface/Member.java new file mode 100644 index 000000000..1097d1906 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/Member.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; + +/** + * Interface representing members of class files (that is, fields and methods). + */ +public interface Member extends HasAttribute { + /** + * Get the defining class. + * + * @return {@code non-null;} the defining class + */ + public CstType getDefiningClass(); + + /** + * Get the field {@code access_flags}. + * + * @return the access flags + */ + public int getAccessFlags(); + + /** + * Get the field {@code name_index} of the member. This is + * just a convenient shorthand for {@code getNat().getName()}. + * + * @return {@code non-null;} the name + */ + public CstString getName(); + + /** + * Get the field {@code descriptor_index} of the member. This is + * just a convenient shorthand for {@code getNat().getDescriptor()}. + * + * @return {@code non-null;} the descriptor + */ + public CstString getDescriptor(); + + /** + * Get the name and type associated with this member. This is a + * combination of the fields {@code name_index} and + * {@code descriptor_index} in the original classfile, interpreted + * via the constant pool. + * + * @return {@code non-null;} the name and type + */ + public CstNat getNat(); + + /** + * Get the field {@code attributes} (along with + * {@code attributes_count}). + * + * @return {@code non-null;} the constant pool + */ + public AttributeList getAttributes(); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/Method.java b/dexlib/src/main/java/com/android/dx/cf/iface/Method.java new file mode 100644 index 000000000..18b9af64f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/Method.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.rop.type.Prototype; + +/** + * Interface representing methods of class files. + */ +public interface Method + extends Member +{ + /** + * Get the effective method descriptor, which includes, if + * necessary, a first {@code this} parameter. + * + * @return {@code non-null;} the effective method descriptor + */ + public Prototype getEffectiveDescriptor(); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/MethodList.java b/dexlib/src/main/java/com/android/dx/cf/iface/MethodList.java new file mode 100644 index 000000000..dfa6528a6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/MethodList.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +/** + * Interface for lists of methods. + */ +public interface MethodList { + /** + * Get whether this instance is mutable. Note that the + * {@code MethodList} interface itself doesn't provide any means + * of mutation, but that doesn't mean that there isn't a non-interface + * way of mutating an instance. + * + * @return {@code true} iff this instance is somehow mutable + */ + public boolean isMutable(); + + /** + * Get the number of methods in the list. + * + * @return the size + */ + public int size(); + + /** + * Get the {@code n}th method. + * + * @param n {@code n >= 0, n < size();} which method + * @return {@code non-null;} the method in question + */ + public Method get(int n); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/ParseException.java b/dexlib/src/main/java/com/android/dx/cf/iface/ParseException.java new file mode 100644 index 000000000..6ed6d3bbf --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/ParseException.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dex.util.ExceptionWithContext; + +/** + * Exception from parsing. + */ +public class ParseException + extends ExceptionWithContext { + public ParseException(String message) { + super(message); + } + + public ParseException(Throwable cause) { + super(cause); + } + + public ParseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/ParseObserver.java b/dexlib/src/main/java/com/android/dx/cf/iface/ParseObserver.java new file mode 100644 index 000000000..98d5a75c2 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/ParseObserver.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.util.ByteArray; + +/** + * Observer of parsing in action. This is used to supply feedback from + * the various things that parse particularly to the dumping utilities. + */ +public interface ParseObserver { + /** + * Indicate that the level of indentation for a dump should increase + * or decrease (positive or negative argument, respectively). + * + * @param indentDelta the amount to change indentation + */ + public void changeIndent(int indentDelta); + + /** + * Indicate that a particular member is now being parsed. + * + * @param bytes {@code non-null;} the source that is being parsed + * @param offset offset into {@code bytes} for the start of the + * member + * @param name {@code non-null;} name of the member + * @param descriptor {@code non-null;} descriptor of the member + */ + public void startParsingMember(ByteArray bytes, int offset, String name, + String descriptor); + + /** + * Indicate that a particular member is no longer being parsed. + * + * @param bytes {@code non-null;} the source that was parsed + * @param offset offset into {@code bytes} for the end of the + * member + * @param name {@code non-null;} name of the member + * @param descriptor {@code non-null;} descriptor of the member + * @param member {@code non-null;} the actual member that was parsed + */ + public void endParsingMember(ByteArray bytes, int offset, String name, + String descriptor, Member member); + + /** + * Indicate that some parsing happened. + * + * @param bytes {@code non-null;} the source that was parsed + * @param offset offset into {@code bytes} for what was parsed + * @param len number of bytes parsed + * @param human {@code non-null;} human form for what was parsed + */ + public void parsed(ByteArray bytes, int offset, int len, String human); +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdAttributeList.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdAttributeList.java new file mode 100644 index 000000000..287b8c7f3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdAttributeList.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.util.FixedSizeList; + +/** + * Standard implementation of {@link AttributeList}, which directly stores + * an array of {@link Attribute} objects and can be made immutable. + */ +public final class StdAttributeList extends FixedSizeList + implements AttributeList { + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public StdAttributeList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Attribute get(int n) { + return (Attribute) get0(n); + } + + /** {@inheritDoc} */ + public int byteLength() { + int sz = size(); + int result = 2; // u2 attributes_count + + for (int i = 0; i < sz; i++) { + result += get(i).byteLength(); + } + + return result; + } + + /** {@inheritDoc} */ + public Attribute findFirst(String name) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + Attribute att = get(i); + if (att.getName().equals(name)) { + return att; + } + } + + return null; + } + + /** {@inheritDoc} */ + public Attribute findNext(Attribute attrib) { + int sz = size(); + int at; + + outer: { + for (at = 0; at < sz; at++) { + Attribute att = get(at); + if (att == attrib) { + break outer; + } + } + + return null; + } + + String name = attrib.getName(); + + for (at++; at < sz; at++) { + Attribute att = get(at); + if (att.getName().equals(name)) { + return att; + } + } + + return null; + } + + /** + * Sets the attribute at the given index. + * + * @param n {@code >= 0, < size();} which attribute + * @param attribute {@code null-ok;} the attribute object + */ + public void set(int n, Attribute attribute) { + set0(n, attribute); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdField.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdField.java new file mode 100644 index 000000000..ef9873d6f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdField.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.cf.attrib.AttConstantValue; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.cst.TypedConstant; + +/** + * Standard implementation of {@link Field}, which directly stores + * all the associated data. + */ +public final class StdField extends StdMember implements Field { + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the defining class + * @param accessFlags access flags + * @param nat {@code non-null;} member name and type (descriptor) + * @param attributes {@code non-null;} list of associated attributes + */ + public StdField(CstType definingClass, int accessFlags, CstNat nat, + AttributeList attributes) { + super(definingClass, accessFlags, nat, attributes); + } + + /** {@inheritDoc} */ + public TypedConstant getConstantValue() { + AttributeList attribs = getAttributes(); + AttConstantValue cval = (AttConstantValue) + attribs.findFirst(AttConstantValue.ATTRIBUTE_NAME); + + if (cval == null) { + return null; + } + + return cval.getConstantValue(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdFieldList.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdFieldList.java new file mode 100644 index 000000000..f27bd2261 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdFieldList.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.util.FixedSizeList; + +/** + * Standard implementation of {@link FieldList}, which directly stores + * an array of {@link Field} objects and can be made immutable. + */ +public final class StdFieldList extends FixedSizeList implements FieldList { + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public StdFieldList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Field get(int n) { + return (Field) get0(n); + } + + /** + * Sets the field at the given index. + * + * @param n {@code >= 0, < size();} which field + * @param field {@code null-ok;} the field object + */ + public void set(int n, Field field) { + set0(n, field); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdMember.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdMember.java new file mode 100644 index 000000000..e67b216ec --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdMember.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; + +/** + * Standard implementation of {@link Member}, which directly stores + * all the associated data. + */ +public abstract class StdMember implements Member { + /** {@code non-null;} the defining class */ + private final CstType definingClass; + + /** access flags */ + private final int accessFlags; + + /** {@code non-null;} member name and type */ + private final CstNat nat; + + /** {@code non-null;} list of associated attributes */ + private final AttributeList attributes; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the defining class + * @param accessFlags access flags + * @param nat {@code non-null;} member name and type (descriptor) + * @param attributes {@code non-null;} list of associated attributes + */ + public StdMember(CstType definingClass, int accessFlags, CstNat nat, + AttributeList attributes) { + if (definingClass == null) { + throw new NullPointerException("definingClass == null"); + } + + if (nat == null) { + throw new NullPointerException("nat == null"); + } + + if (attributes == null) { + throw new NullPointerException("attributes == null"); + } + + this.definingClass = definingClass; + this.accessFlags = accessFlags; + this.nat = nat; + this.attributes = attributes; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(nat.toHuman()); + sb.append('}'); + + return sb.toString(); + } + + /** {@inheritDoc} */ + public final CstType getDefiningClass() { + return definingClass; + } + + /** {@inheritDoc} */ + public final int getAccessFlags() { + return accessFlags; + } + + /** {@inheritDoc} */ + public final CstNat getNat() { + return nat; + } + + /** {@inheritDoc} */ + public final CstString getName() { + return nat.getName(); + } + + /** {@inheritDoc} */ + public final CstString getDescriptor() { + return nat.getDescriptor(); + } + + /** {@inheritDoc} */ + public final AttributeList getAttributes() { + return attributes; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdMethod.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdMethod.java new file mode 100644 index 000000000..c511d7d0a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdMethod.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Prototype; + +/** + * Standard implementation of {@link Method}, which directly stores + * all the associated data. + */ +public final class StdMethod extends StdMember implements Method { + /** {@code non-null;} the effective method descriptor */ + private final Prototype effectiveDescriptor; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the defining class + * @param accessFlags access flags + * @param nat {@code non-null;} member name and type (descriptor) + * @param attributes {@code non-null;} list of associated attributes + */ + public StdMethod(CstType definingClass, int accessFlags, CstNat nat, + AttributeList attributes) { + super(definingClass, accessFlags, nat, attributes); + + String descStr = getDescriptor().getString(); + effectiveDescriptor = + Prototype.intern(descStr, definingClass.getClassType(), + AccessFlags.isStatic(accessFlags), + nat.isInstanceInit()); + } + + /** {@inheritDoc} */ + public Prototype getEffectiveDescriptor() { + return effectiveDescriptor; + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/StdMethodList.java b/dexlib/src/main/java/com/android/dx/cf/iface/StdMethodList.java new file mode 100644 index 000000000..417cdeee1 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/StdMethodList.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.cf.iface; + +import com.android.dx.util.FixedSizeList; + +/** + * Standard implementation of {@link MethodList}, which directly stores + * an array of {@link Method} objects and can be made immutable. + */ +public final class StdMethodList extends FixedSizeList implements MethodList { + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public StdMethodList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Method get(int n) { + return (Method) get0(n); + } + + /** + * Sets the method at the given index. + * + * @param n {@code >= 0, < size();} which method + * @param method {@code null-ok;} the method object + */ + public void set(int n, Method method) { + set0(n, method); + } +} diff --git a/dexlib/src/main/java/com/android/dx/cf/iface/package.html b/dexlib/src/main/java/com/android/dx/cf/iface/package.html new file mode 100644 index 000000000..c7345527d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/cf/iface/package.html @@ -0,0 +1,10 @@ + +

Interfaces and base classes for dealing with class files. This package +doesn't have any parsing but does have basic container implementations.

+ +

PACKAGES USED: +

    +
  • com.android.dx.rop.pool
  • +
  • com.android.dx.util
  • +
+ diff --git a/dexlib/src/main/java/com/android/dx/command/UsageException.java b/dexlib/src/main/java/com/android/dx/command/UsageException.java new file mode 100644 index 000000000..6809bf45e --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/command/UsageException.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.command; + +/** + * Simple exception class used to communicate that the command-line tool + * should print the usage message. + */ +public class UsageException extends RuntimeException { + // This space intentionally left blank. +} diff --git a/dexlib/src/main/java/com/android/dx/command/dexer/DxContext.java b/dexlib/src/main/java/com/android/dx/command/dexer/DxContext.java new file mode 100644 index 000000000..fe972f93d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/command/dexer/DxContext.java @@ -0,0 +1,35 @@ +package com.android.dx.command.dexer; + +import com.android.dx.dex.cf.CodeStatistics; +import com.android.dx.dex.cf.OptimizerOptions; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +/** + * State used by a single invocation of {@link Main}. + */ +public class DxContext { + public final CodeStatistics codeStatistics = new CodeStatistics(); + public final OptimizerOptions optimizerOptions = new OptimizerOptions(); + public final PrintStream out; + public final PrintStream err; + + @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") + final PrintStream noop = new PrintStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + // noop; + } + }); + + public DxContext(OutputStream out, OutputStream err) { + this.out = new PrintStream(out); + this.err = new PrintStream(err); + } + + public DxContext() { + this(System.out, System.err); + } +} diff --git a/dexlib/src/main/java/com/android/dx/command/dexer/Main.java b/dexlib/src/main/java/com/android/dx/command/dexer/Main.java new file mode 100644 index 000000000..ad8cd7bff --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/command/dexer/Main.java @@ -0,0 +1,1953 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.command.dexer; + +import com.android.dex.Dex; +import com.android.dex.DexException; +import com.android.dex.DexFormat; +import com.android.dex.util.FileUtils; +import com.android.dx.Version; +import com.android.dx.cf.code.SimException; +import com.android.dx.cf.direct.ClassPathOpener; +import com.android.dx.cf.direct.ClassPathOpener.FileNameFilter; +import com.android.dx.cf.direct.DirectClassFile; +import com.android.dx.cf.direct.StdAttributeFactory; +import com.android.dx.cf.iface.ParseException; +import com.android.dx.command.UsageException; +import com.android.dx.dex.DexOptions; +import com.android.dx.dex.cf.CfOptions; +import com.android.dx.dex.cf.CfTranslator; +import com.android.dx.dex.code.PositionList; +import com.android.dx.dex.file.ClassDefItem; +import com.android.dx.dex.file.DexFile; +import com.android.dx.dex.file.EncodedMethod; +import com.android.dx.merge.CollisionPolicy; +import com.android.dx.merge.DexMerger; +import com.android.dx.rop.annotation.Annotation; +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.Type; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +/** + * Main class for the class file translator. + */ +public class Main { + + public static final int CONCURRENCY_LEVEL = 4; + + /** + * File extension of a {@code .dex} file. + */ + private static final String DEX_EXTENSION = ".dex"; + + /** + * File name prefix of a {@code .dex} file automatically loaded in an + * archive. + */ + private static final String DEX_PREFIX = "classes"; + + /** + * {@code non-null;} the lengthy message that tries to discourage + * people from defining core classes in applications + */ + private static final String IN_RE_CORE_CLASSES = + "Ill-advised or mistaken usage of a core class (java.* or javax.*)\n" + + "when not building a core library.\n\n" + + "This is often due to inadvertently including a core library file\n" + + "in your application's project, when using an IDE (such as\n" + + "Eclipse). If you are sure you're not intentionally defining a\n" + + "core class, then this is the most likely explanation of what's\n" + + "going on.\n\n" + + "However, you might actually be trying to define a class in a core\n" + + "namespace, the source of which you may have taken, for example,\n" + + "from a non-Android virtual machine project. This will most\n" + + "assuredly not work. At a minimum, it jeopardizes the\n" + + "compatibility of your app with future versions of the platform.\n" + + "It is also often of questionable legality.\n\n" + + "If you really intend to build a core library -- which is only\n" + + "appropriate as part of creating a full virtual machine\n" + + "distribution, as opposed to compiling an application -- then use\n" + + "the \"--core-library\" option to suppress this error message.\n\n" + + "If you go ahead and use \"--core-library\" but are in fact\n" + + "building an application, then be forewarned that your application\n" + + "will still fail to build or run, at some point. Please be\n" + + "prepared for angry customers who find, for example, that your\n" + + "application ceases to function once they upgrade their operating\n" + + "system. You will be to blame for this problem.\n\n" + + "If you are legitimately using some code that happens to be in a\n" + + "core package, then the easiest safe alternative you have is to\n" + + "repackage that code. That is, move the classes in question into\n" + + "your own package namespace. This means that they will never be in\n" + + "conflict with core system classes. JarJar is a tool that may help\n" + + "you in this endeavor. If you find that you cannot do this, then\n" + + "that is an indication that the path you are on will ultimately\n" + + "lead to pain, suffering, grief, and lamentation.\n"; + + /** + * {@code non-null;} name of the standard manifest file in {@code .jar} + * files + */ + private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; + + /** + * {@code non-null;} attribute name for the (quasi-standard?) + * {@code Created-By} attribute + */ + private static final Attributes.Name CREATED_BY = + new Attributes.Name("Created-By"); + + /** + * {@code non-null;} list of {@code javax} subpackages that are considered + * to be "core". Note:: This list must be sorted, since it + * is binary-searched. + */ + private static final String[] JAVAX_CORE = { + "accessibility", "crypto", "imageio", "management", "naming", "net", + "print", "rmi", "security", "sip", "sound", "sql", "swing", + "transaction", "xml" + }; + + /* Array.newInstance may be added by RopperMachine, + * ArrayIndexOutOfBoundsException. may be added by EscapeAnalysis */ + private static final int MAX_METHOD_ADDED_DURING_DEX_CREATION = 2; + + /* .TYPE */ + private static final int MAX_FIELD_ADDED_DURING_DEX_CREATION = 9; + + /** number of errors during processing */ + private AtomicInteger errors = new AtomicInteger(0); + + /** {@code non-null;} parsed command-line arguments */ + private Arguments args; + + /** {@code non-null;} output file in-progress */ + private DexFile outputDex; + + /** + * {@code null-ok;} map of resources to include in the output, or + * {@code null} if resources are being ignored + */ + private TreeMap outputResources; + + /** Library .dex files to merge into the output .dex. */ + private final List libraryDexBuffers = new ArrayList(); + + /** Thread pool object used for multi-thread class translation. */ + private ExecutorService classTranslatorPool; + + /** Single thread executor, for collecting results of parallel translation, + * and adding classes to dex file in original input file order. */ + private ExecutorService classDefItemConsumer; + + /** Futures for {@code classDefItemConsumer} tasks. */ + private List> addToDexFutures = + new ArrayList>(); + + /** Thread pool object used for multi-thread dex conversion (to byte array). + * Used in combination with multi-dex support, to allow outputing + * a completed dex file, in parallel with continuing processing. */ + private ExecutorService dexOutPool; + + /** Futures for {@code dexOutPool} task. */ + private List> dexOutputFutures = new ArrayList>(); + + /** Lock object used to to coordinate dex file rotation, and + * multi-threaded translation. */ + private Object dexRotationLock = new Object(); + + /** Record the number if method indices "reserved" for files + * committed to translation in the context of the current dex + * file, but not yet added. */ + private int maxMethodIdsInProcess = 0; + + /** Record the number if field indices "reserved" for files + * committed to translation in the context of the current dex + * file, but not yet added. */ + private int maxFieldIdsInProcess = 0; + + /** true if any files are successfully processed */ + private volatile boolean anyFilesProcessed; + + /** class files older than this must be defined in the target dex file. */ + private long minimumFileAge = 0; + + private Set classesInMainDex = null; + + private List dexOutputArrays = new ArrayList(); + + private OutputStreamWriter humanOutWriter = null; + + private final DxContext context; + + public Main(DxContext context) { + this.context = context; + } + + /** + * Run and exit if something unexpected happened. + * @param argArray the command line arguments + */ + public static void main(String[] argArray) throws IOException { + DxContext context = new DxContext(); + Arguments arguments = new Arguments(context); + arguments.parse(argArray); + + int result = new Main(context).runDx(arguments); + + if (result != 0) { + throw new IOException(); + } + } + + public static void clearInternTables() { + Prototype.clearInternTable(); + RegisterSpec.clearInternTable(); + CstType.clearInternTable(); + Type.clearInternTable(); + } + + /** + * Run and return a result code. + * @param arguments the data + parameters for the conversion + * @return 0 if success > 0 otherwise. + */ + public static int run(Arguments arguments) throws IOException { + return new Main(new DxContext()).runDx(arguments); + } + + public int runDx(Arguments arguments) throws IOException { + + // Reset the error count to start fresh. + errors.set(0); + // empty the list, so that tools that load dx and keep it around + // for multiple runs don't reuse older buffers. + libraryDexBuffers.clear(); + + args = arguments; + args.makeOptionsObjects(); + + OutputStream humanOutRaw = null; + if (args.humanOutName != null) { + humanOutRaw = openOutput(args.humanOutName); + humanOutWriter = new OutputStreamWriter(humanOutRaw); + } + + try { + if (args.multiDex) { + return runMultiDex(); + } else { + return runMonoDex(); + } + } finally { + closeOutput(humanOutRaw); + } + } + + private int runMonoDex() throws IOException { + + File incrementalOutFile = null; + if (args.incremental) { + if (args.outName == null) { + context.err.println( + "error: no incremental output name specified"); + return -1; + } + incrementalOutFile = new File(args.outName); + if (incrementalOutFile.exists()) { + minimumFileAge = incrementalOutFile.lastModified(); + } + } + + if (!processAllFiles()) { + return 1; + } + + if (args.incremental && !anyFilesProcessed) { + return 0; // this was a no-op incremental build + } + + // this array is null if no classes were defined + byte[] outArray = null; + + if (!outputDex.isEmpty() || (args.humanOutName != null)) { + outArray = writeDex(outputDex); + + if (outArray == null) { + return 2; + } + } + + if (args.incremental) { + outArray = mergeIncremental(outArray, incrementalOutFile); + } + + outArray = mergeLibraryDexBuffers(outArray); + + if (args.jarOutput) { + // Effectively free up the (often massive) DexFile memory. + outputDex = null; + + if (outArray != null) { + outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray); + } + if (!createJar(args.outName)) { + return 3; + } + } else if (outArray != null && args.outName != null) { + OutputStream out = openOutput(args.outName); + out.write(outArray); + closeOutput(out); + } + + return 0; + } + + private int runMultiDex() throws IOException { + + assert !args.incremental; + + if (args.mainDexListFile != null) { + classesInMainDex = new HashSet(); + readPathsFromFile(args.mainDexListFile, classesInMainDex); + } + + dexOutPool = Executors.newFixedThreadPool(args.numThreads); + + if (!processAllFiles()) { + return 1; + } + + if (!libraryDexBuffers.isEmpty()) { + throw new DexException("Library dex files are not supported in multi-dex mode"); + } + + if (outputDex != null) { + // this array is null if no classes were defined + + dexOutputFutures.add(dexOutPool.submit(new DexWriter(outputDex))); + + // Effectively free up the (often massive) DexFile memory. + outputDex = null; + } + try { + dexOutPool.shutdown(); + if (!dexOutPool.awaitTermination(600L, TimeUnit.SECONDS)) { + throw new RuntimeException("Timed out waiting for dex writer threads."); + } + + for (Future f : dexOutputFutures) { + dexOutputArrays.add(f.get()); + } + + } catch (InterruptedException ex) { + dexOutPool.shutdownNow(); + throw new RuntimeException("A dex writer thread has been interrupted."); + } catch (Exception e) { + dexOutPool.shutdownNow(); + throw new RuntimeException("Unexpected exception in dex writer thread"); + } + + if (args.jarOutput) { + for (int i = 0; i < dexOutputArrays.size(); i++) { + outputResources.put(getDexFileName(i), + dexOutputArrays.get(i)); + } + + if (!createJar(args.outName)) { + return 3; + } + } else if (args.outName != null) { + File outDir = new File(args.outName); + assert outDir.isDirectory(); + for (int i = 0; i < dexOutputArrays.size(); i++) { + OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i))); + try { + out.write(dexOutputArrays.get(i)); + } finally { + closeOutput(out); + } + } + } + + return 0; + } + + private static String getDexFileName(int i) { + if (i == 0) { + return DexFormat.DEX_IN_JAR_NAME; + } else { + return DEX_PREFIX + (i + 1) + DEX_EXTENSION; + } + } + + private static void readPathsFromFile(String fileName, Collection paths) throws IOException { + BufferedReader bfr = null; + try { + FileReader fr = new FileReader(fileName); + bfr = new BufferedReader(fr); + + String line; + + while (null != (line = bfr.readLine())) { + paths.add(fixPath(line)); + } + + } finally { + if (bfr != null) { + bfr.close(); + } + } + } + + /** + * Merges the dex files {@code update} and {@code base}, preferring + * {@code update}'s definition for types defined in both dex files. + * + * @param base a file to find the previous dex file. May be a .dex file, a + * jar file possibly containing a .dex file, or null. + * @return the bytes of the merged dex file, or null if both the update + * and the base dex do not exist. + */ + private byte[] mergeIncremental(byte[] update, File base) throws IOException { + Dex dexA = null; + Dex dexB = null; + + if (update != null) { + dexA = new Dex(update); + } + + if (base.exists()) { + dexB = new Dex(base); + } + + Dex result; + if (dexA == null && dexB == null) { + return null; + } else if (dexA == null) { + result = dexB; + } else if (dexB == null) { + result = dexA; + } else { + result = new DexMerger(new Dex[] {dexA, dexB}, CollisionPolicy.KEEP_FIRST, context).merge(); + } + + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + result.writeTo(bytesOut); + return bytesOut.toByteArray(); + } + + /** + * Merges the dex files in library jars. If multiple dex files define the + * same type, this fails with an exception. + */ + private byte[] mergeLibraryDexBuffers(byte[] outArray) throws IOException { + ArrayList dexes = new ArrayList(); + if (outArray != null) { + dexes.add(new Dex(outArray)); + } + for (byte[] libraryDex : libraryDexBuffers) { + dexes.add(new Dex(libraryDex)); + } + if (dexes.isEmpty()) { + return null; + } + Dex merged = new DexMerger(dexes.toArray(new Dex[dexes.size()]), CollisionPolicy.FAIL, context).merge(); + return merged.getBytes(); + } + + /** + * Constructs the output {@link DexFile}, fill it in with all the + * specified classes, and populate the resources map if required. + * + * @return whether processing was successful + */ + private boolean processAllFiles() { + createDexFile(); + + if (args.jarOutput) { + outputResources = new TreeMap(); + } + + anyFilesProcessed = false; + String[] fileNames = args.fileNames; + Arrays.sort(fileNames); + + // translate classes in parallel + classTranslatorPool = new ThreadPoolExecutor(args.numThreads, + args.numThreads, 0, TimeUnit.SECONDS, + new ArrayBlockingQueue(2 * args.numThreads, true), + new ThreadPoolExecutor.CallerRunsPolicy()); + // collect translated and write to dex in order + classDefItemConsumer = Executors.newSingleThreadExecutor(); + + + try { + if (args.mainDexListFile != null) { + // with --main-dex-list + FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() : + new BestEffortMainDexListFilter(); + + // forced in main dex + for (int i = 0; i < fileNames.length; i++) { + processOne(fileNames[i], mainPassFilter); + } + + if (dexOutputFutures.size() > 0) { + throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION + + ", main dex capacity exceeded"); + } + + if (args.minimalMainDex) { + // start second pass directly in a secondary dex file. + + // Wait for classes in progress to complete + synchronized(dexRotationLock) { + while(maxMethodIdsInProcess > 0 || maxFieldIdsInProcess > 0) { + try { + dexRotationLock.wait(); + } catch(InterruptedException ex) { + /* ignore */ + } + } + } + + rotateDexFile(); + } + + // remaining files + for (int i = 0; i < fileNames.length; i++) { + processOne(fileNames[i], new NotFilter(mainPassFilter)); + } + } else { + // without --main-dex-list + for (int i = 0; i < fileNames.length; i++) { + processOne(fileNames[i], ClassPathOpener.acceptAll); + } + } + } catch (StopProcessing ex) { + /* + * Ignore it and just let the error reporting do + * their things. + */ + } + + try { + classTranslatorPool.shutdown(); + classTranslatorPool.awaitTermination(600L, TimeUnit.SECONDS); + classDefItemConsumer.shutdown(); + classDefItemConsumer.awaitTermination(600L, TimeUnit.SECONDS); + + for (Future f : addToDexFutures) { + try { + f.get(); + } catch(ExecutionException ex) { + // Catch any previously uncaught exceptions from + // class translation and adding to dex. + int count = errors.incrementAndGet(); + if (count < 10) { + if (args.debug) { + context.err.println("Uncaught translation error:"); + ex.getCause().printStackTrace(context.err); + } else { + context.err.println("Uncaught translation error: " + ex.getCause()); + } + } else { + throw new InterruptedException("Too many errors"); + } + } + } + + } catch (InterruptedException ie) { + classTranslatorPool.shutdownNow(); + classDefItemConsumer.shutdownNow(); + throw new RuntimeException("Translation has been interrupted", ie); + } catch (Exception e) { + classTranslatorPool.shutdownNow(); + classDefItemConsumer.shutdownNow(); + e.printStackTrace(context.out); + throw new RuntimeException("Unexpected exception in translator thread.", e); + } + + int errorNum = errors.get(); + if (errorNum != 0) { + context.err.println(errorNum + " error" + + ((errorNum == 1) ? "" : "s") + "; aborting"); + return false; + } + + if (args.incremental && !anyFilesProcessed) { + return true; + } + + if (!(anyFilesProcessed || args.emptyOk)) { + context.err.println("no classfiles specified"); + return false; + } + + if (args.optimize && args.statistics) { + context.codeStatistics.dumpStatistics(context.out); + } + + return true; + } + + private void createDexFile() { + outputDex = new DexFile(args.dexOptions); + + if (args.dumpWidth != 0) { + outputDex.setDumpWidth(args.dumpWidth); + } + } + + private void rotateDexFile() { + if (outputDex != null) { + if (dexOutPool != null) { + dexOutputFutures.add(dexOutPool.submit(new DexWriter(outputDex))); + } else { + dexOutputArrays.add(writeDex(outputDex)); + } + } + + createDexFile(); + } + + /** + * Processes one pathname element. + * + * @param pathname {@code non-null;} the pathname to process. May + * be the path of a class file, a jar file, or a directory + * containing class files. + * @param filter {@code non-null;} A filter for excluding files. + */ + private void processOne(String pathname, FileNameFilter filter) { + ClassPathOpener opener; + + opener = new ClassPathOpener(pathname, true, filter, new FileBytesConsumer()); + + if (opener.process()) { + updateStatus(true); + } + } + + private void updateStatus(boolean res) { + anyFilesProcessed |= res; + } + + + /** + * Processes one file, which may be either a class or a resource. + * + * @param name {@code non-null;} name of the file + * @param bytes {@code non-null;} contents of the file + * @return whether processing was successful + */ + private boolean processFileBytes(String name, long lastModified, byte[] bytes) { + + boolean isClass = name.endsWith(".class"); + boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME); + boolean keepResources = (outputResources != null); + + if (!isClass && !isClassesDex && !keepResources) { + if (args.verbose) { + context.out.println("ignored resource " + name); + } + return false; + } + + if (args.verbose) { + context.out.println("processing " + name + "..."); + } + + String fixedName = fixPath(name); + + if (isClass) { + + if (keepResources && args.keepClassesInJar) { + synchronized (outputResources) { + outputResources.put(fixedName, bytes); + } + } + if (lastModified < minimumFileAge) { + return true; + } + processClass(fixedName, bytes); + // Assume that an exception may occur. Status will be updated + // asynchronously, if the class compiles without error. + return false; + } else if (isClassesDex) { + synchronized (libraryDexBuffers) { + libraryDexBuffers.add(bytes); + } + return true; + } else { + synchronized (outputResources) { + outputResources.put(fixedName, bytes); + } + return true; + } + } + + /** + * Processes one classfile. + * + * @param name {@code non-null;} name of the file, clipped such that it + * should correspond to the name of the class it contains + * @param bytes {@code non-null;} contents of the file + * @return whether processing was successful + */ + private boolean processClass(String name, byte[] bytes) { + if (! args.coreLibrary) { + checkClassName(name); + } + + try { + new DirectClassFileConsumer(name, bytes, null).call( + new ClassParserTask(name, bytes).call()); + } catch (ParseException ex) { + // handled in FileBytesConsumer + throw ex; + } catch(Exception ex) { + throw new RuntimeException("Exception parsing classes", ex); + } + + return true; + } + + + private DirectClassFile parseClass(String name, byte[] bytes) { + + DirectClassFile cf = new DirectClassFile(bytes, name, + args.cfOptions.strictNameCheck); + cf.setAttributeFactory(StdAttributeFactory.THE_ONE); + cf.getMagic(); // triggers the actual parsing + return cf; + } + + private ClassDefItem translateClass(byte[] bytes, DirectClassFile cf) { + try { + return CfTranslator.translate(context, cf, bytes, args.cfOptions, + args.dexOptions, outputDex); + } catch (ParseException ex) { + context.err.println("\ntrouble processing:"); + if (args.debug) { + ex.printStackTrace(context.err); + } else { + ex.printContext(context.err); + } + } + errors.incrementAndGet(); + return null; + } + + private boolean addClassToDex(ClassDefItem clazz) { + synchronized (outputDex) { + outputDex.add(clazz); + } + return true; + } + + /** + * Check the class name to make sure it's not a "core library" + * class. If there is a problem, this updates the error count and + * throws an exception to stop processing. + * + * @param name {@code non-null;} the fully-qualified internal-form + * class name + */ + private void checkClassName(String name) { + boolean bogus = false; + + if (name.startsWith("java/")) { + bogus = true; + } else if (name.startsWith("javax/")) { + int slashAt = name.indexOf('/', 6); + if (slashAt == -1) { + // Top-level javax classes are verboten. + bogus = true; + } else { + String pkg = name.substring(6, slashAt); + bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0); + } + } + + if (! bogus) { + return; + } + + /* + * The user is probably trying to include an entire desktop + * core library in a misguided attempt to get their application + * working. Try to help them understand what's happening. + */ + + context.err.println("\ntrouble processing \"" + name + "\":\n\n" + + IN_RE_CORE_CLASSES); + errors.incrementAndGet(); + throw new StopProcessing(); + } + + /** + * Converts {@link #outputDex} into a {@code byte[]} and do whatever + * human-oriented dumping is required. + * + * @return {@code null-ok;} the converted {@code byte[]} or {@code null} + * if there was a problem + */ + private byte[] writeDex(DexFile outputDex) { + byte[] outArray = null; + + try { + try { + if (args.methodToDump != null) { + /* + * Simply dump the requested method. Note: The call + * to toDex() is required just to get the underlying + * structures ready. + */ + outputDex.toDex(null, false); + dumpMethod(outputDex, args.methodToDump, humanOutWriter); + } else { + /* + * This is the usual case: Create an output .dex file, + * and write it, dump it, etc. + */ + outArray = outputDex.toDex(humanOutWriter, args.verboseDump); + } + + if (args.statistics) { + context.out.println(outputDex.getStatistics().toHuman()); + } + } finally { + if (humanOutWriter != null) { + humanOutWriter.flush(); + } + } + } catch (Exception ex) { + if (args.debug) { + context.err.println("\ntrouble writing output:"); + ex.printStackTrace(context.err); + } else { + context.err.println("\ntrouble writing output: " + + ex.getMessage()); + } + return null; + } + return outArray; + } + + /** + * Creates a jar file from the resources (including dex file arrays). + * + * @param fileName {@code non-null;} name of the file + * @return whether the creation was successful + */ + private boolean createJar(String fileName) { + /* + * Make or modify the manifest (as appropriate), put the dex + * array into the resources map, and then process the entire + * resources map in a uniform manner. + */ + + try { + Manifest manifest = makeManifest(); + OutputStream out = openOutput(fileName); + JarOutputStream jarOut = new JarOutputStream(out, manifest); + + try { + for (Map.Entry e : + outputResources.entrySet()) { + String name = e.getKey(); + byte[] contents = e.getValue(); + JarEntry entry = new JarEntry(name); + int length = contents.length; + + if (args.verbose) { + context.out.println("writing " + name + "; size " + length + "..."); + } + + entry.setSize(length); + jarOut.putNextEntry(entry); + jarOut.write(contents); + jarOut.closeEntry(); + } + } finally { + jarOut.finish(); + jarOut.flush(); + closeOutput(out); + } + } catch (Exception ex) { + if (args.debug) { + context.err.println("\ntrouble writing output:"); + ex.printStackTrace(context.err); + } else { + context.err.println("\ntrouble writing output: " + + ex.getMessage()); + } + return false; + } + + return true; + } + + /** + * Creates and returns the manifest to use for the output. This may + * modify {@link #outputResources} (removing the pre-existing manifest). + * + * @return {@code non-null;} the manifest + */ + private Manifest makeManifest() throws IOException { + byte[] manifestBytes = outputResources.get(MANIFEST_NAME); + Manifest manifest; + Attributes attribs; + + if (manifestBytes == null) { + // We need to construct an entirely new manifest. + manifest = new Manifest(); + attribs = manifest.getMainAttributes(); + attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + } else { + manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); + attribs = manifest.getMainAttributes(); + outputResources.remove(MANIFEST_NAME); + } + + String createdBy = attribs.getValue(CREATED_BY); + if (createdBy == null) { + createdBy = ""; + } else { + createdBy += " + "; + } + createdBy += "dx " + Version.VERSION; + + attribs.put(CREATED_BY, createdBy); + attribs.putValue("Dex-Location", DexFormat.DEX_IN_JAR_NAME); + + return manifest; + } + + /** + * Opens and returns the named file for writing, treating "-" specially. + * + * @param name {@code non-null;} the file name + * @return {@code non-null;} the opened file + */ + private OutputStream openOutput(String name) throws IOException { + if (name.equals("-") || + name.startsWith("-.")) { + return context.out; + } + + return new FileOutputStream(name); + } + + /** + * Flushes and closes the given output stream, except if it happens to be + * {@link System#out} in which case this method does the flush but not + * the close. This method will also silently do nothing if given a + * {@code null} argument. + * + * @param stream {@code null-ok;} what to close + */ + private void closeOutput(OutputStream stream) throws IOException { + if (stream == null) { + return; + } + + stream.flush(); + + if (stream != context.out) { + stream.close(); + } + } + + /** + * Returns the "fixed" version of a given file path, suitable for + * use as a path within a {@code .jar} file and for checking + * against a classfile-internal "this class" name. This looks for + * the last instance of the substring {@code "/./"} within + * the path, and if it finds it, it takes the portion after to be + * the fixed path. If that isn't found but the path starts with + * {@code "./"}, then that prefix is removed and the rest is + * return. If neither of these is the case, this method returns + * its argument. + * + * @param path {@code non-null;} the path to "fix" + * @return {@code non-null;} the fixed version (which might be the same as + * the given {@code path}) + */ + private static String fixPath(String path) { + /* + * If the path separator is \ (like on windows), we convert the + * path to a standard '/' separated path. + */ + if (File.separatorChar == '\\') { + path = path.replace('\\', '/'); + } + + int index = path.lastIndexOf("/./"); + + if (index != -1) { + return path.substring(index + 3); + } + + if (path.startsWith("./")) { + return path.substring(2); + } + + return path; + } + + /** + * Dumps any method with the given name in the given file. + * + * @param dex {@code non-null;} the dex file + * @param fqName {@code non-null;} the fully-qualified name of the + * method(s) + * @param out {@code non-null;} where to dump to + */ + private void dumpMethod(DexFile dex, String fqName, + OutputStreamWriter out) { + boolean wildcard = fqName.endsWith("*"); + int lastDot = fqName.lastIndexOf('.'); + + if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) { + context.err.println("bogus fully-qualified method name: " + + fqName); + return; + } + + String className = fqName.substring(0, lastDot).replace('.', '/'); + String methodName = fqName.substring(lastDot + 1); + ClassDefItem clazz = dex.getClassOrNull(className); + + if (clazz == null) { + context.err.println("no such class: " + className); + return; + } + + if (wildcard) { + methodName = methodName.substring(0, methodName.length() - 1); + } + + ArrayList allMeths = clazz.getMethods(); + TreeMap meths = + new TreeMap(); + + /* + * Figure out which methods to include in the output, and get them + * all sorted, so that the printout code is robust with respect to + * changes in the underlying order. + */ + for (EncodedMethod meth : allMeths) { + String methName = meth.getName().getString(); + if ((wildcard && methName.startsWith(methodName)) || + (!wildcard && methName.equals(methodName))) { + meths.put(meth.getRef().getNat(), meth); + } + } + + if (meths.size() == 0) { + context.err.println("no such method: " + fqName); + return; + } + + PrintWriter pw = new PrintWriter(out); + + for (EncodedMethod meth : meths.values()) { + // TODO: Better stuff goes here, perhaps. + meth.debugPrint(pw, args.verboseDump); + + /* + * The (default) source file is an attribute of the class, but + * it's useful to see it in method dumps. + */ + CstString sourceFile = clazz.getSourceFile(); + if (sourceFile != null) { + pw.println(" source file: " + sourceFile.toQuoted()); + } + + Annotations methodAnnotations = + clazz.getMethodAnnotations(meth.getRef()); + AnnotationsList parameterAnnotations = + clazz.getParameterAnnotations(meth.getRef()); + + if (methodAnnotations != null) { + pw.println(" method annotations:"); + for (Annotation a : methodAnnotations.getAnnotations()) { + pw.println(" " + a); + } + } + + if (parameterAnnotations != null) { + pw.println(" parameter annotations:"); + int sz = parameterAnnotations.size(); + for (int i = 0; i < sz; i++) { + pw.println(" parameter " + i); + Annotations annotations = parameterAnnotations.get(i); + for (Annotation a : annotations.getAnnotations()) { + pw.println(" " + a); + } + } + } + } + + pw.flush(); + } + + private static class NotFilter implements FileNameFilter { + private final FileNameFilter filter; + + private NotFilter(FileNameFilter filter) { + this.filter = filter; + } + + @Override + public boolean accept(String path) { + return !filter.accept(path); + } + } + + /** + * A quick and accurate filter for when file path can be trusted. + */ + private class MainDexListFilter implements FileNameFilter { + + @Override + public boolean accept(String fullPath) { + if (fullPath.endsWith(".class")) { + String path = fixPath(fullPath); + return classesInMainDex.contains(path); + } else { + return true; + } + } + } + + /** + * A best effort conservative filter for when file path can not be trusted. + */ + private class BestEffortMainDexListFilter implements FileNameFilter { + + Map> map = new HashMap>(); + + public BestEffortMainDexListFilter() { + for (String pathOfClass : classesInMainDex) { + String normalized = fixPath(pathOfClass); + String simple = getSimpleName(normalized); + List fullPath = map.get(simple); + if (fullPath == null) { + fullPath = new ArrayList(1); + map.put(simple, fullPath); + } + fullPath.add(normalized); + } + } + + @Override + public boolean accept(String path) { + if (path.endsWith(".class")) { + String normalized = fixPath(path); + String simple = getSimpleName(normalized); + List fullPaths = map.get(simple); + if (fullPaths != null) { + for (String fullPath : fullPaths) { + if (normalized.endsWith(fullPath)) { + return true; + } + } + } + return false; + } else { + return true; + } + } + + private String getSimpleName(String path) { + int index = path.lastIndexOf('/'); + if (index >= 0) { + return path.substring(index + 1); + } else { + return path; + } + } + } + + /** + * Exception class used to halt processing prematurely. + */ + private static class StopProcessing extends RuntimeException { + // This space intentionally left blank. + } + + /** + * Command-line argument parser and access. + */ + public static class Arguments { + + private static final String MINIMAL_MAIN_DEX_OPTION = "--minimal-main-dex"; + + private static final String MAIN_DEX_LIST_OPTION = "--main-dex-list"; + + private static final String MULTI_DEX_OPTION = "--multi-dex"; + + private static final String NUM_THREADS_OPTION = "--num-threads"; + + private static final String INCREMENTAL_OPTION = "--incremental"; + + private static final String INPUT_LIST_OPTION = "--input-list"; + + public final DxContext context; + + /** whether to run in debug mode */ + public boolean debug = false; + + /** whether to emit warning messages */ + public boolean warnings = true; + + /** whether to emit high-level verbose human-oriented output */ + public boolean verbose = false; + + /** whether to emit verbose human-oriented output in the dump file */ + public boolean verboseDump = false; + + /** whether we are constructing a core library */ + public boolean coreLibrary = false; + + /** {@code null-ok;} particular method to dump */ + public String methodToDump = null; + + /** max width for columnar output */ + public int dumpWidth = 0; + + /** {@code null-ok;} output file name for binary file */ + public String outName = null; + + /** {@code null-ok;} output file name for human-oriented dump */ + public String humanOutName = null; + + /** whether strict file-name-vs-class-name checking should be done */ + public boolean strictNameCheck = true; + + /** + * whether it is okay for there to be no {@code .class} files + * to process + */ + public boolean emptyOk = false; + + /** + * whether the binary output is to be a {@code .jar} file + * instead of a plain {@code .dex} + */ + public boolean jarOutput = false; + + /** + * when writing a {@code .jar} file, whether to still + * keep the {@code .class} files + */ + public boolean keepClassesInJar = false; + + /** what API level to target */ + public int minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES; + + /** how much source position info to preserve */ + public int positionInfo = PositionList.LINES; + + /** whether to keep local variable information */ + public boolean localInfo = true; + + /** whether to merge with the output dex file if it exists. */ + public boolean incremental = false; + + /** whether to force generation of const-string/jumbo for all indexes, + * to allow merges between dex files with many strings. */ + public boolean forceJumbo = false; + + /** {@code non-null} after {@link #parse}; file name arguments */ + public String[] fileNames; + + /** whether to do SSA/register optimization */ + public boolean optimize = true; + + /** Filename containg list of methods to optimize */ + public String optimizeListFile = null; + + /** Filename containing list of methods to NOT optimize */ + public String dontOptimizeListFile = null; + + /** Whether to print statistics to stdout at end of compile cycle */ + public boolean statistics; + + /** Options for class file transformation */ + public CfOptions cfOptions; + + /** Options for dex file output */ + public DexOptions dexOptions; + + /** number of threads to run with */ + public int numThreads = 1; + + /** generation of multiple dex is allowed */ + public boolean multiDex = false; + + /** Optional file containing a list of class files containing classes to be forced in main + * dex */ + public String mainDexListFile = null; + + /** Produce the smallest possible main dex. Ignored unless multiDex is true and + * mainDexListFile is specified and non empty. */ + public boolean minimalMainDex = false; + + public int maxNumberOfIdxPerDex = DexFormat.MAX_MEMBER_IDX + 1; + + /** Optional list containing inputs read in from a file. */ + private List inputList = null; + + private boolean outputIsDirectory = false; + private boolean outputIsDirectDex = false; + + public Arguments(DxContext context) { + this.context = context; + } + + public Arguments() { + this(new DxContext()); + } + + private static class ArgumentsParser { + + /** The arguments to process. */ + private final String[] arguments; + /** The index of the next argument to process. */ + private int index; + /** The current argument being processed after a {@link #getNext()} call. */ + private String current; + /** The last value of an argument processed by {@link #isArg(String)}. */ + private String lastValue; + + public ArgumentsParser(String[] arguments) { + this.arguments = arguments; + index = 0; + } + + public String getCurrent() { + return current; + } + + public String getLastValue() { + return lastValue; + } + + /** + * Moves on to the next argument. + * Returns false when we ran out of arguments that start with --. + */ + public boolean getNext() { + if (index >= arguments.length) { + return false; + } + current = arguments[index]; + if (current.equals("--") || !current.startsWith("--")) { + return false; + } + index++; + return true; + } + + /** + * Similar to {@link #getNext()}, this moves on the to next argument. + * It does not check however whether the argument starts with -- + * and thus can be used to retrieve values. + */ + private boolean getNextValue() { + if (index >= arguments.length) { + return false; + } + current = arguments[index]; + index++; + return true; + } + + /** + * Returns all the arguments that have not been processed yet. + */ + public String[] getRemaining() { + int n = arguments.length - index; + String[] remaining = new String[n]; + if (n > 0) { + System.arraycopy(arguments, index, remaining, 0, n); + } + return remaining; + } + + /** + * Checks the current argument against the given prefix. + * If prefix is in the form '--name=', an extra value is expected. + * The argument can then be in the form '--name=value' or as a 2-argument + * form '--name value'. + */ + public boolean isArg(String prefix) { + int n = prefix.length(); + if (n > 0 && prefix.charAt(n-1) == '=') { + // Argument accepts a value. Capture it. + if (current.startsWith(prefix)) { + // Argument is in the form --name=value, split the value out + lastValue = current.substring(n); + return true; + } else { + // Check whether we have "--name value" as 2 arguments + prefix = prefix.substring(0, n-1); + if (current.equals(prefix)) { + if (getNextValue()) { + lastValue = current; + return true; + } else { + System.err.println("Missing value after parameter " + prefix); + throw new UsageException(); + } + } + return false; + } + } else { + // Argument does not accept a value. + return current.equals(prefix); + } + } + } + + private void parseFlags(ArgumentsParser parser) { + + while(parser.getNext()) { + if (parser.isArg("--debug")) { + debug = true; + } else if (parser.isArg("--no-warning")) { + warnings = false; + } else if (parser.isArg("--verbose")) { + verbose = true; + } else if (parser.isArg("--verbose-dump")) { + verboseDump = true; + } else if (parser.isArg("--no-files")) { + emptyOk = true; + } else if (parser.isArg("--no-optimize")) { + optimize = false; + } else if (parser.isArg("--no-strict")) { + strictNameCheck = false; + } else if (parser.isArg("--core-library")) { + coreLibrary = true; + } else if (parser.isArg("--statistics")) { + statistics = true; + } else if (parser.isArg("--optimize-list=")) { + if (dontOptimizeListFile != null) { + context.err.println("--optimize-list and " + + "--no-optimize-list are incompatible."); + throw new UsageException(); + } + optimize = true; + optimizeListFile = parser.getLastValue(); + } else if (parser.isArg("--no-optimize-list=")) { + if (dontOptimizeListFile != null) { + context.err.println("--optimize-list and " + + "--no-optimize-list are incompatible."); + throw new UsageException(); + } + optimize = true; + dontOptimizeListFile = parser.getLastValue(); + } else if (parser.isArg("--keep-classes")) { + keepClassesInJar = true; + } else if (parser.isArg("--output=")) { + outName = parser.getLastValue(); + if (new File(outName).isDirectory()) { + jarOutput = false; + outputIsDirectory = true; + } else if (FileUtils.hasArchiveSuffix(outName)) { + jarOutput = true; + } else if (outName.endsWith(".dex") || + outName.equals("-")) { + jarOutput = false; + outputIsDirectDex = true; + } else { + context.err.println("unknown output extension: " + + outName); + throw new UsageException(); + } + } else if (parser.isArg("--dump-to=")) { + humanOutName = parser.getLastValue(); + } else if (parser.isArg("--dump-width=")) { + dumpWidth = Integer.parseInt(parser.getLastValue()); + } else if (parser.isArg("--dump-method=")) { + methodToDump = parser.getLastValue(); + jarOutput = false; + } else if (parser.isArg("--positions=")) { + String pstr = parser.getLastValue().intern(); + if (pstr == "none") { + positionInfo = PositionList.NONE; + } else if (pstr == "important") { + positionInfo = PositionList.IMPORTANT; + } else if (pstr == "lines") { + positionInfo = PositionList.LINES; + } else { + context.err.println("unknown positions option: " + + pstr); + throw new UsageException(); + } + } else if (parser.isArg("--no-locals")) { + localInfo = false; + } else if (parser.isArg(NUM_THREADS_OPTION + "=")) { + numThreads = Integer.parseInt(parser.getLastValue()); + } else if (parser.isArg(INCREMENTAL_OPTION)) { + incremental = true; + } else if (parser.isArg("--force-jumbo")) { + forceJumbo = true; + } else if (parser.isArg(MULTI_DEX_OPTION)) { + multiDex = true; + } else if (parser.isArg(MAIN_DEX_LIST_OPTION + "=")) { + mainDexListFile = parser.getLastValue(); + } else if (parser.isArg(MINIMAL_MAIN_DEX_OPTION)) { + minimalMainDex = true; + } else if (parser.isArg("--set-max-idx-number=")) { // undocumented test option + maxNumberOfIdxPerDex = Integer.parseInt(parser.getLastValue()); + } else if(parser.isArg(INPUT_LIST_OPTION + "=")) { + File inputListFile = new File(parser.getLastValue()); + try { + inputList = new ArrayList(); + readPathsFromFile(inputListFile.getAbsolutePath(), inputList); + } catch (IOException e) { + context.err.println( + "Unable to read input list file: " + inputListFile.getName()); + // problem reading the file so we should halt execution + throw new UsageException(); + } + } else if (parser.isArg("--min-sdk-version=")) { + String arg = parser.getLastValue(); + int value; + try { + value = Integer.parseInt(arg); + } catch (NumberFormatException ex) { + value = -1; + } + if (value < 1) { + System.err.println("improper min-sdk-version option: " + arg); + throw new UsageException(); + } + minSdkVersion = value; + } else { + context.err.println("unknown option: " + parser.getCurrent()); + throw new UsageException(); + } + } + } + + + /** + * Parses all command-line arguments and updates the state of the {@code Arguments} object + * accordingly. + * + * @param args {@code non-null;} the arguments + */ + private void parse(String[] args) { + ArgumentsParser parser = new ArgumentsParser(args); + + parseFlags(parser); + + fileNames = parser.getRemaining(); + if(inputList != null && !inputList.isEmpty()) { + // append the file names to the end of the input list + inputList.addAll(Arrays.asList(fileNames)); + fileNames = inputList.toArray(new String[inputList.size()]); + } + + if (fileNames.length == 0) { + if (!emptyOk) { + context.err.println("no input files specified"); + throw new UsageException(); + } + } else if (emptyOk) { + context.out.println("ignoring input files"); + } + + if ((humanOutName == null) && (methodToDump != null)) { + humanOutName = "-"; + } + + if (mainDexListFile != null && !multiDex) { + context.err.println(MAIN_DEX_LIST_OPTION + " is only supported in combination with " + + MULTI_DEX_OPTION); + throw new UsageException(); + } + + if (minimalMainDex && (mainDexListFile == null || !multiDex)) { + context.err.println(MINIMAL_MAIN_DEX_OPTION + " is only supported in combination with " + + MULTI_DEX_OPTION + " and " + MAIN_DEX_LIST_OPTION); + throw new UsageException(); + } + + if (multiDex && incremental) { + context.err.println(INCREMENTAL_OPTION + " is not supported with " + + MULTI_DEX_OPTION); + throw new UsageException(); + } + + if (multiDex && outputIsDirectDex) { + context.err.println("Unsupported output \"" + outName +"\". " + MULTI_DEX_OPTION + + " supports only archive or directory output"); + throw new UsageException(); + } + + if (outputIsDirectory && !multiDex) { + outName = new File(outName, DexFormat.DEX_IN_JAR_NAME).getPath(); + } + + makeOptionsObjects(); + } + + /** + * Parses only command-line flags and updates the state of the {@code Arguments} object + * accordingly. + * + * @param flags {@code non-null;} the flags + */ + public void parseFlags(String[] flags) { + parseFlags(new ArgumentsParser(flags)); + } + + /** + * Copies relevant arguments over into CfOptions and DexOptions instances. + */ + public void makeOptionsObjects() { + cfOptions = new CfOptions(); + cfOptions.positionInfo = positionInfo; + cfOptions.localInfo = localInfo; + cfOptions.strictNameCheck = strictNameCheck; + cfOptions.optimize = optimize; + cfOptions.optimizeListFile = optimizeListFile; + cfOptions.dontOptimizeListFile = dontOptimizeListFile; + cfOptions.statistics = statistics; + + if (warnings) { + cfOptions.warn = context.err; + } else { + cfOptions.warn = context.noop; + } + + dexOptions = new DexOptions(); + dexOptions.minSdkVersion = minSdkVersion; + dexOptions.forceJumbo = forceJumbo; + } + } + + /** + * Callback class for processing input file bytes, produced by the + * ClassPathOpener. + */ + private class FileBytesConsumer implements ClassPathOpener.Consumer { + + @Override + public boolean processFileBytes(String name, long lastModified, + byte[] bytes) { + return Main.this.processFileBytes(name, lastModified, bytes); + } + + @Override + public void onException(Exception ex) { + if (ex instanceof StopProcessing) { + throw (StopProcessing) ex; + } else if (ex instanceof SimException) { + context.err.println("\nEXCEPTION FROM SIMULATION:"); + context.err.println(ex.getMessage() + "\n"); + context.err.println(((SimException) ex).getContext()); + } else if (ex instanceof ParseException) { + context.err.println("\nPARSE ERROR:"); + ParseException parseException = (ParseException) ex; + if (args.debug) { + parseException.printStackTrace(context.err); + } else { + parseException.printContext(context.err); + } + } else { + context.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); + ex.printStackTrace(context.err); + } + errors.incrementAndGet(); + } + + @Override + public void onProcessArchiveStart(File file) { + if (args.verbose) { + context.out.println("processing archive " + file + "..."); + } + } + } + + /** Callable helper class to parse class bytes. */ + private class ClassParserTask implements Callable { + + String name; + byte[] bytes; + + private ClassParserTask(String name, byte[] bytes) { + this.name = name; + this.bytes = bytes; + } + + @Override + public DirectClassFile call() throws Exception { + DirectClassFile cf = parseClass(name, bytes); + + return cf; + } + } + + /** + * Callable helper class used to sequentially collect the results of + * the (optionally parallel) translation phase, in correct input file order. + * This class is also responsible for coordinating dex file rotation + * with the ClassDefItemConsumer class. + * We maintain invariant that the number of indices used in the current + * dex file plus the max number of indices required by classes passed to + * the translation phase and not yet added to the dex file, is less than + * or equal to the dex file limit. + * For each parsed file, we estimate the maximum number of indices it may + * require. If passing the file to the translation phase would invalidate + * the invariant, we wait, until the next class is added to the dex file, + * and then reevaluate the invariant. If there are no further classes in + * the translation phase, we rotate the dex file. + */ + private class DirectClassFileConsumer implements Callable { + + String name; + byte[] bytes; + Future dcff; + + private DirectClassFileConsumer(String name, byte[] bytes, + Future dcff) { + this.name = name; + this.bytes = bytes; + this.dcff = dcff; + } + + @Override + public Boolean call() throws Exception { + + DirectClassFile cf = dcff.get(); + return call(cf); + } + + private Boolean call(DirectClassFile cf) { + + int maxMethodIdsInClass = 0; + int maxFieldIdsInClass = 0; + + if (args.multiDex) { + + // Calculate max number of indices this class will add to the + // dex file. + // The possibility of overloading means that we can't easily + // know how many constant are needed for declared methods and + // fields. We therefore make the simplifying assumption that + // all constants are external method or field references. + + int constantPoolSize = cf.getConstantPool().size(); + maxMethodIdsInClass = constantPoolSize + cf.getMethods().size() + + MAX_METHOD_ADDED_DURING_DEX_CREATION; + maxFieldIdsInClass = constantPoolSize + cf.getFields().size() + + MAX_FIELD_ADDED_DURING_DEX_CREATION; + synchronized(dexRotationLock) { + + int numMethodIds; + int numFieldIds; + // Number of indices used in current dex file. + synchronized(outputDex) { + numMethodIds = outputDex.getMethodIds().items().size(); + numFieldIds = outputDex.getFieldIds().items().size(); + } + // Wait until we're sure this class will fit in the current + // dex file. + while(((numMethodIds + maxMethodIdsInClass + maxMethodIdsInProcess + > args.maxNumberOfIdxPerDex) || + (numFieldIds + maxFieldIdsInClass + maxFieldIdsInProcess + > args.maxNumberOfIdxPerDex))) { + + if (maxMethodIdsInProcess > 0 || maxFieldIdsInProcess > 0) { + // There are classes in the translation phase that + // have not yet been added to the dex file, so we + // wait for the next class to complete. + try { + dexRotationLock.wait(); + } catch(InterruptedException ex) { + /* ignore */ + } + } else if (outputDex.getClassDefs().items().size() > 0) { + // There are no further classes in the translation + // phase, and we have a full dex file. Rotate! + rotateDexFile(); + } else { + // The estimated number of indices is too large for + // an empty dex file. We proceed hoping the actual + // number of indices needed will fit. + break; + } + synchronized(outputDex) { + numMethodIds = outputDex.getMethodIds().items().size(); + numFieldIds = outputDex.getFieldIds().items().size(); + } + } + // Add our estimate to the total estimate for + // classes under translation. + maxMethodIdsInProcess += maxMethodIdsInClass; + maxFieldIdsInProcess += maxFieldIdsInClass; + } + } + + // Submit class to translation phase. + Future cdif = classTranslatorPool.submit( + new ClassTranslatorTask(name, bytes, cf)); + Future res = classDefItemConsumer.submit(new ClassDefItemConsumer( + name, cdif, maxMethodIdsInClass, maxFieldIdsInClass)); + addToDexFutures.add(res); + + return true; + } + } + + + /** Callable helper class to translate classes in parallel */ + private class ClassTranslatorTask implements Callable { + + String name; + byte[] bytes; + DirectClassFile classFile; + + private ClassTranslatorTask(String name, byte[] bytes, + DirectClassFile classFile) { + this.name = name; + this.bytes = bytes; + this.classFile = classFile; + } + + @Override + public ClassDefItem call() { + ClassDefItem clazz = translateClass(bytes, classFile); + return clazz; + } + } + + /** + * Callable helper class used to collect the results of + * the parallel translation phase, adding the translated classes to + * the current dex file in correct (deterministic) file order. + * This class is also responsible for coordinating dex file rotation + * with the DirectClassFileConsumer class. + */ + private class ClassDefItemConsumer implements Callable { + + String name; + Future futureClazz; + int maxMethodIdsInClass; + int maxFieldIdsInClass; + + private ClassDefItemConsumer(String name, Future futureClazz, + int maxMethodIdsInClass, int maxFieldIdsInClass) { + this.name = name; + this.futureClazz = futureClazz; + this.maxMethodIdsInClass = maxMethodIdsInClass; + this.maxFieldIdsInClass = maxFieldIdsInClass; + } + + @Override + public Boolean call() throws Exception { + try { + ClassDefItem clazz = futureClazz.get(); + if (clazz != null) { + addClassToDex(clazz); + updateStatus(true); + } + return true; + } catch(ExecutionException ex) { + // Rethrow previously uncaught translation exceptions. + // These, as well as any exceptions from addClassToDex, + // are handled and reported in processAllFiles(). + Throwable t = ex.getCause(); + throw (t instanceof Exception) ? (Exception) t : ex; + } finally { + if (args.multiDex) { + // Having added our actual indicies to the dex file, + // we subtract our original estimate from the total estimate, + // and signal the translation phase, which may be paused + // waiting to determine if more classes can be added to the + // current dex file, or if a new dex file must be created. + synchronized(dexRotationLock) { + maxMethodIdsInProcess -= maxMethodIdsInClass; + maxFieldIdsInProcess -= maxFieldIdsInClass; + dexRotationLock.notifyAll(); + } + } + } + } + } + + /** Callable helper class to convert dex files in worker threads */ + private class DexWriter implements Callable { + + private DexFile dexFile; + + private DexWriter(DexFile dexFile) { + this.dexFile = dexFile; + } + + @Override + public byte[] call() throws IOException { + return writeDex(dexFile); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/DexOptions.java b/dexlib/src/main/java/com/android/dx/dex/DexOptions.java new file mode 100644 index 000000000..19dd84e0f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/DexOptions.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex; + +import com.android.dex.DexFormat; + +import com.android.dx.dex.code.DalvInsnList; + +/** + * Container for options used to control details of dex file generation. + */ +public class DexOptions { + + /** + * Enable alignment support of 64-bit registers on Dalvik even registers. This is a temporary + * configuration flag allowing to quickly go back on the default behavior to face up to problem. + */ + public static final boolean ALIGN_64BIT_REGS_SUPPORT = true; + + /** + * Does final processing of 64-bit alignment into output finisher to gets output as + * {@link DalvInsnList} with 64-bit registers aligned at best. Disabled the final processing is + * required for tools such as Dasm to avoid modifying user inputs. + */ + public boolean ALIGN_64BIT_REGS_IN_OUTPUT_FINISHER = ALIGN_64BIT_REGS_SUPPORT; + + /** minimum SDK version targeted */ + public int minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES; + + /** force generation of jumbo opcodes */ + public boolean forceJumbo = false; + + /** + * Gets the dex file magic number corresponding to this instance. + */ + public String getMagic() { + return DexFormat.apiToMagic(minSdkVersion); + } + + /** + * Returns whether default and static interface methods are allowed. + * + * This became allowed as of Nougat (SDK version 24). + * + * @return true if supported on the currently selected SDK. + */ + public boolean canUseDefaultInterfaceMethods() { + return minSdkVersion >= DexFormat.API_DEFAULT_INTERFACE_METHODS; + } + + /** + * Returns whether invoke-polymorphic can be used. This is emitted for calls + * to {@code java.lang.invoke.MethodHandle.invoke()} and + * {@code java.lang.invoke.MethodHandle.invokeExact()}. + * + * This became allowed as of the Android O release (SDK version 26). + * + * @return true if supported on the currently selected SDK. + */ + public boolean canUseInvokePolymorphic() { + return minSdkVersion >= DexFormat.API_INVOKE_POLYMORPHIC; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/cf/AttributeTranslator.java b/dexlib/src/main/java/com/android/dx/dex/cf/AttributeTranslator.java new file mode 100644 index 000000000..31f19c007 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/cf/AttributeTranslator.java @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.cf; + +import com.android.dx.cf.attrib.AttAnnotationDefault; +import com.android.dx.cf.attrib.AttEnclosingMethod; +import com.android.dx.cf.attrib.AttExceptions; +import com.android.dx.cf.attrib.AttInnerClasses; +import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations; +import com.android.dx.cf.attrib.AttRuntimeInvisibleParameterAnnotations; +import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations; +import com.android.dx.cf.attrib.AttRuntimeVisibleParameterAnnotations; +import com.android.dx.cf.attrib.AttSignature; +import com.android.dx.cf.attrib.AttSourceDebugExtension; +import com.android.dx.cf.attrib.InnerClassList; +import com.android.dx.cf.direct.DirectClassFile; +import com.android.dx.cf.iface.AttributeList; +import com.android.dx.cf.iface.Method; +import com.android.dx.cf.iface.MethodList; +import com.android.dx.dex.file.AnnotationUtils; +import com.android.dx.rop.annotation.Annotation; +import com.android.dx.rop.annotation.AnnotationVisibility; +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.rop.annotation.NameValuePair; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.Warning; +import java.util.ArrayList; + +/** + * Utility methods that translate various classfile attributes + * into forms suitable for use in creating {@code dex} files. + */ +/*package*/ class AttributeTranslator { + /** + * This class is uninstantiable. + */ + private AttributeTranslator() { + // This space intentionally left blank. + } + + /** + * Gets the list of thrown exceptions for a given method. + * + * @param method {@code non-null;} the method in question + * @return {@code non-null;} the list of thrown exceptions + */ + public static TypeList getExceptions(Method method) { + AttributeList attribs = method.getAttributes(); + AttExceptions exceptions = (AttExceptions) + attribs.findFirst(AttExceptions.ATTRIBUTE_NAME); + + if (exceptions == null) { + return StdTypeList.EMPTY; + } + + return exceptions.getExceptions(); + } + + /** + * Gets the annotations out of a given {@link AttributeList}. This + * combines both visible and invisible annotations into a single + * result set and also adds in a system annotation for the + * {@code Signature} attribute if present. + * + * @param attribs {@code non-null;} the attributes list to search in + * @return {@code non-null;} the set of annotations, which may be empty + */ + public static Annotations getAnnotations(AttributeList attribs) { + Annotations result = getAnnotations0(attribs); + Annotation signature = getSignature(attribs); + Annotation sourceDebugExtension = getSourceDebugExtension(attribs); + + if (signature != null) { + result = Annotations.combine(result, signature); + } + + if (sourceDebugExtension != null) { + result = Annotations.combine(result, sourceDebugExtension); + } + + return result; + } + + /** + * Gets the annotations out of a given class, similar to {@link + * #getAnnotations}, also including annotations for translations + * of class-level attributes {@code EnclosingMethod} and + * {@code InnerClasses}, if present. Additionally, if the + * class is an annotation class, then this also includes a + * representation of all the {@code AnnotationDefault} + * values. + * + * @param cf {@code non-null;} the class in question + * @param args {@code non-null;} the high-level options + * @return {@code non-null;} the set of annotations, which may be empty + */ + public static Annotations getClassAnnotations(DirectClassFile cf, + CfOptions args) { + CstType thisClass = cf.getThisClass(); + AttributeList attribs = cf.getAttributes(); + Annotations result = getAnnotations(attribs); + Annotation enclosingMethod = translateEnclosingMethod(attribs); + + try { + Annotations innerClassAnnotations = + translateInnerClasses(thisClass, attribs, + enclosingMethod == null); + if (innerClassAnnotations != null) { + result = Annotations.combine(result, innerClassAnnotations); + } + } catch (Warning warn) { + args.warn.println("warning: " + warn.getMessage()); + } + + if (enclosingMethod != null) { + result = Annotations.combine(result, enclosingMethod); + } + + if (AccessFlags.isAnnotation(cf.getAccessFlags())) { + Annotation annotationDefault = + translateAnnotationDefaults(cf); + if (annotationDefault != null) { + result = Annotations.combine(result, annotationDefault); + } + } + + return result; + } + + /** + * Gets the annotations out of a given method, similar to {@link + * #getAnnotations}, also including an annotation for the translation + * of the method-specific attribute {@code Exceptions}. + * + * @param method {@code non-null;} the method in question + * @return {@code non-null;} the set of annotations, which may be empty + */ + public static Annotations getMethodAnnotations(Method method) { + Annotations result = getAnnotations(method.getAttributes()); + TypeList exceptions = getExceptions(method); + + if (exceptions.size() != 0) { + Annotation throwsAnnotation = + AnnotationUtils.makeThrows(exceptions); + result = Annotations.combine(result, throwsAnnotation); + } + + return result; + } + + /** + * Helper method for {@link #getAnnotations} which just gets the + * existing annotations, per se. + * + * @param attribs {@code non-null;} the attributes list to search in + * @return {@code non-null;} the set of annotations, which may be empty + */ + private static Annotations getAnnotations0(AttributeList attribs) { + AttRuntimeVisibleAnnotations visible = + (AttRuntimeVisibleAnnotations) + attribs.findFirst(AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME); + AttRuntimeInvisibleAnnotations invisible = + (AttRuntimeInvisibleAnnotations) + attribs.findFirst(AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME); + + if (visible == null) { + if (invisible == null) { + return Annotations.EMPTY; + } + return invisible.getAnnotations(); + } + + if (invisible == null) { + return visible.getAnnotations(); + } + + // Both are non-null, so combine them. + + return Annotations.combine(visible.getAnnotations(), + invisible.getAnnotations()); + } + + /** + * Gets the {@code Signature} attribute out of a given + * {@link AttributeList}, if any, translating it to an annotation. + * + * @param attribs {@code non-null;} the attributes list to search in + * @return {@code null-ok;} the converted {@code Signature} annotation, + * if there was an attribute to translate + */ + private static Annotation getSignature(AttributeList attribs) { + AttSignature signature = (AttSignature) + attribs.findFirst(AttSignature.ATTRIBUTE_NAME); + + if (signature == null) { + return null; + } + + return AnnotationUtils.makeSignature(signature.getSignature()); + } + + + private static Annotation getSourceDebugExtension(AttributeList attribs) { + AttSourceDebugExtension extension = (AttSourceDebugExtension) + attribs.findFirst(AttSourceDebugExtension.ATTRIBUTE_NAME); + + if (extension == null) { + return null; + } + + return AnnotationUtils.makeSourceDebugExtension(extension.getSmapString()); + } + + /** + * Gets the {@code EnclosingMethod} attribute out of a given + * {@link AttributeList}, if any, translating it to an annotation. + * If the class really has an enclosing method, this returns an + * {@code EnclosingMethod} annotation; if not, this returns + * an {@code EnclosingClass} annotation. + * + * @param attribs {@code non-null;} the attributes list to search in + * @return {@code null-ok;} the converted {@code EnclosingMethod} or + * {@code EnclosingClass} annotation, if there was an + * attribute to translate + */ + private static Annotation translateEnclosingMethod(AttributeList attribs) { + AttEnclosingMethod enclosingMethod = (AttEnclosingMethod) + attribs.findFirst(AttEnclosingMethod.ATTRIBUTE_NAME); + + if (enclosingMethod == null) { + return null; + } + + CstType enclosingClass = enclosingMethod.getEnclosingClass(); + CstNat nat = enclosingMethod.getMethod(); + + if (nat == null) { + /* + * Dalvik doesn't use EnclosingMethod annotations unless + * there really is an enclosing method. Anonymous classes + * are unambiguously identified by having an InnerClass + * annotation with an empty name along with an appropriate + * EnclosingClass. + */ + return AnnotationUtils.makeEnclosingClass(enclosingClass); + } + + return AnnotationUtils.makeEnclosingMethod( + new CstMethodRef(enclosingClass, nat)); + } + + /** + * Gets the {@code InnerClasses} attribute out of a given + * {@link AttributeList}, if any, translating it to one or more of an + * {@code InnerClass}, {@code EnclosingClass}, or + * {@code MemberClasses} annotation. + * + * @param thisClass {@code non-null;} type representing the class being + * processed + * @param attribs {@code non-null;} the attributes list to search in + * @param needEnclosingClass whether to include an + * {@code EnclosingClass} annotation + * @return {@code null-ok;} the converted list of annotations, if there + * was an attribute to translate + */ + private static Annotations translateInnerClasses(CstType thisClass, + AttributeList attribs, boolean needEnclosingClass) { + AttInnerClasses innerClasses = (AttInnerClasses) + attribs.findFirst(AttInnerClasses.ATTRIBUTE_NAME); + + if (innerClasses == null) { + return null; + } + + /* + * Search the list for the element representing the current class + * as well as for any named member classes. + */ + + InnerClassList list = innerClasses.getInnerClasses(); + int size = list.size(); + InnerClassList.Item foundThisClass = null; + ArrayList membersList = new ArrayList(); + + for (int i = 0; i < size; i++) { + InnerClassList.Item item = list.get(i); + CstType innerClass = item.getInnerClass(); + if (innerClass.equals(thisClass)) { + foundThisClass = item; + } else if (thisClass.equals(item.getOuterClass())) { + membersList.add(innerClass.getClassType()); + } + } + + int membersSize = membersList.size(); + + if ((foundThisClass == null) && (membersSize == 0)) { + return null; + } + + Annotations result = new Annotations(); + + if (foundThisClass != null) { + result.add(AnnotationUtils.makeInnerClass( + foundThisClass.getInnerName(), + foundThisClass.getAccessFlags())); + if (needEnclosingClass) { + CstType outer = foundThisClass.getOuterClass(); + if (outer == null) { + throw new Warning( + "Ignoring InnerClasses attribute for an " + + "anonymous inner class\n" + + "(" + thisClass.toHuman() + + ") that doesn't come with an\n" + + "associated EnclosingMethod attribute. " + + "This class was probably produced by a\n" + + "compiler that did not target the modern " + + ".class file format. The recommended\n" + + "solution is to recompile the class from " + + "source, using an up-to-date compiler\n" + + "and without specifying any \"-target\" type " + + "options. The consequence of ignoring\n" + + "this warning is that reflective operations " + + "on this class will incorrectly\n" + + "indicate that it is *not* an inner class."); + } + result.add(AnnotationUtils.makeEnclosingClass( + foundThisClass.getOuterClass())); + } + } + + if (membersSize != 0) { + StdTypeList typeList = new StdTypeList(membersSize); + for (int i = 0; i < membersSize; i++) { + typeList.set(i, membersList.get(i)); + } + typeList.setImmutable(); + result.add(AnnotationUtils.makeMemberClasses(typeList)); + } + + result.setImmutable(); + return result; + } + + /** + * Gets the parameter annotations out of a given method. This + * combines both visible and invisible annotations into a single + * result set. + * + * @param method {@code non-null;} the method in question + * @return {@code non-null;} the list of annotation sets, which may be + * empty + */ + public static AnnotationsList getParameterAnnotations(Method method) { + AttributeList attribs = method.getAttributes(); + AttRuntimeVisibleParameterAnnotations visible = + (AttRuntimeVisibleParameterAnnotations) + attribs.findFirst( + AttRuntimeVisibleParameterAnnotations.ATTRIBUTE_NAME); + AttRuntimeInvisibleParameterAnnotations invisible = + (AttRuntimeInvisibleParameterAnnotations) + attribs.findFirst( + AttRuntimeInvisibleParameterAnnotations.ATTRIBUTE_NAME); + + if (visible == null) { + if (invisible == null) { + return AnnotationsList.EMPTY; + } + return invisible.getParameterAnnotations(); + } + + if (invisible == null) { + return visible.getParameterAnnotations(); + } + + // Both are non-null, so combine them. + + return AnnotationsList.combine(visible.getParameterAnnotations(), + invisible.getParameterAnnotations()); + } + + /** + * Gets the {@code AnnotationDefault} attributes out of a + * given class, if any, reforming them as an + * {@code AnnotationDefault} annotation. + * + * @param cf {@code non-null;} the class in question + * @return {@code null-ok;} an appropriately-constructed + * {@code AnnotationDefault} annotation, if there were any + * annotation defaults in the class, or {@code null} if not + */ + private static Annotation translateAnnotationDefaults(DirectClassFile cf) { + CstType thisClass = cf.getThisClass(); + MethodList methods = cf.getMethods(); + int sz = methods.size(); + Annotation result = + new Annotation(thisClass, AnnotationVisibility.EMBEDDED); + boolean any = false; + + for (int i = 0; i < sz; i++) { + Method one = methods.get(i); + AttributeList attribs = one.getAttributes(); + AttAnnotationDefault oneDefault = (AttAnnotationDefault) + attribs.findFirst(AttAnnotationDefault.ATTRIBUTE_NAME); + + if (oneDefault != null) { + NameValuePair pair = new NameValuePair( + one.getNat().getName(), + oneDefault.getValue()); + result.add(pair); + any = true; + } + } + + if (! any) { + return null; + } + + result.setImmutable(); + return AnnotationUtils.makeAnnotationDefault(result); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/cf/CfOptions.java b/dexlib/src/main/java/com/android/dx/dex/cf/CfOptions.java new file mode 100644 index 000000000..03d1d29ca --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/cf/CfOptions.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.cf; + +import com.android.dx.dex.code.PositionList; +import java.io.PrintStream; + +/** + * A class to contain options passed into dex.cf + */ +public class CfOptions { + /** how much source position info to preserve */ + public int positionInfo = PositionList.LINES; + + /** whether to keep local variable information */ + public boolean localInfo = false; + + /** whether strict file-name-vs-class-name checking should be done */ + public boolean strictNameCheck = true; + + /** whether to do SSA/register optimization */ + public boolean optimize = false; + + /** filename containing list of methods to optimize */ + public String optimizeListFile = null; + + /** filename containing list of methods not to optimize */ + public String dontOptimizeListFile = null; + + /** whether to print statistics to stdout at end of compile cycle */ + public boolean statistics; + + /** where to issue warnings to */ + public PrintStream warn = System.err; +} diff --git a/dexlib/src/main/java/com/android/dx/dex/cf/CfTranslator.java b/dexlib/src/main/java/com/android/dx/dex/cf/CfTranslator.java new file mode 100644 index 000000000..5b797d4e3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/cf/CfTranslator.java @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.cf; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.cf.code.ConcreteMethod; +import com.android.dx.cf.code.Ropper; +import com.android.dx.cf.direct.DirectClassFile; +import com.android.dx.cf.iface.Field; +import com.android.dx.cf.iface.FieldList; +import com.android.dx.cf.iface.Method; +import com.android.dx.cf.iface.MethodList; +import com.android.dx.command.dexer.DxContext; +import com.android.dx.dex.DexOptions; +import com.android.dx.dex.code.DalvCode; +import com.android.dx.dex.code.PositionList; +import com.android.dx.dex.code.RopTranslator; +import com.android.dx.dex.file.ClassDefItem; +import com.android.dx.dex.file.DexFile; +import com.android.dx.dex.file.EncodedField; +import com.android.dx.dex.file.EncodedMethod; +import com.android.dx.dex.file.FieldIdsSection; +import com.android.dx.dex.file.MethodIdsSection; +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.code.DexTranslationAdvice; +import com.android.dx.rop.code.LocalVariableExtractor; +import com.android.dx.rop.code.LocalVariableInfo; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.TranslationAdvice; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.ConstantPool; +import com.android.dx.rop.cst.CstBaseMethodRef; +import com.android.dx.rop.cst.CstBoolean; +import com.android.dx.rop.cst.CstByte; +import com.android.dx.rop.cst.CstChar; +import com.android.dx.rop.cst.CstEnumRef; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstInterfaceMethodRef; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstShort; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.cst.TypedConstant; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.ssa.Optimizer; + +/** + * Static method that turns {@code byte[]}s containing Java + * classfiles into {@link ClassDefItem} instances. + */ +public class CfTranslator { + /** set to {@code true} to enable development-time debugging code */ + private static final boolean DEBUG = false; + + /** + * This class is uninstantiable. + */ + private CfTranslator() { + // This space intentionally left blank. + } + + /** + * Takes a {@code byte[]}, interprets it as a Java classfile, and + * translates it into a {@link ClassDefItem}. + * + * @param context {@code non-null;} the state global to this invocation. + * @param cf {@code non-null;} the class file + * @param bytes {@code non-null;} contents of the file + * @param cfOptions options for class translation + * @param dexOptions options for dex output + * @param dexFile {@code non-null;} dex output + * @return {@code non-null;} the translated class + */ + public static ClassDefItem translate(DxContext context, DirectClassFile cf, byte[] bytes, + CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) { + try { + return translate0(context, cf, bytes, cfOptions, dexOptions, dexFile); + } catch (RuntimeException ex) { + String msg = "...while processing " + cf.getFilePath(); + throw ExceptionWithContext.withContext(ex, msg); + } + } + + /** + * Performs the main act of translation. This method is separated + * from {@link #translate} just to keep things a bit simpler in + * terms of exception handling. + * + * + * @param context {@code non-null;} the state global to this invocation. + * @param cf {@code non-null;} the class file + * @param bytes {@code non-null;} contents of the file + * @param cfOptions options for class translation + * @param dexOptions options for dex output + * @param dexFile {@code non-null;} dex output + * @return {@code non-null;} the translated class + */ + private static ClassDefItem translate0(DxContext context, DirectClassFile cf, byte[] bytes, + CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) { + + context.optimizerOptions.loadOptimizeLists(cfOptions.optimizeListFile, + cfOptions.dontOptimizeListFile); + + // Build up a class to output. + + CstType thisClass = cf.getThisClass(); + int classAccessFlags = cf.getAccessFlags() & ~AccessFlags.ACC_SUPER; + CstString sourceFile = (cfOptions.positionInfo == PositionList.NONE) ? null : + cf.getSourceFile(); + ClassDefItem out = + new ClassDefItem(thisClass, classAccessFlags, + cf.getSuperclass(), cf.getInterfaces(), sourceFile); + + Annotations classAnnotations = + AttributeTranslator.getClassAnnotations(cf, cfOptions); + if (classAnnotations.size() != 0) { + out.setClassAnnotations(classAnnotations, dexFile); + } + + FieldIdsSection fieldIdsSection = dexFile.getFieldIds(); + MethodIdsSection methodIdsSection = dexFile.getMethodIds(); + processFields(cf, out, dexFile); + processMethods(context, cf, cfOptions, dexOptions, out, dexFile); + + // intern constant pool method, field and type references + ConstantPool constantPool = cf.getConstantPool(); + int constantPoolSize = constantPool.size(); + + for (int i = 0; i < constantPoolSize; i++) { + Constant constant = constantPool.getOrNull(i); + if (constant instanceof CstMethodRef) { + methodIdsSection.intern((CstBaseMethodRef) constant); + } else if (constant instanceof CstInterfaceMethodRef) { + methodIdsSection.intern(((CstInterfaceMethodRef) constant).toMethodRef()); + } else if (constant instanceof CstFieldRef) { + fieldIdsSection.intern((CstFieldRef) constant); + } else if (constant instanceof CstEnumRef) { + fieldIdsSection.intern(((CstEnumRef) constant).getFieldRef()); + } + } + + return out; + } + + /** + * Processes the fields of the given class. + * + * @param cf {@code non-null;} class being translated + * @param out {@code non-null;} output class + * @param dexFile {@code non-null;} dex output + */ + private static void processFields( + DirectClassFile cf, ClassDefItem out, DexFile dexFile) { + CstType thisClass = cf.getThisClass(); + FieldList fields = cf.getFields(); + int sz = fields.size(); + + for (int i = 0; i < sz; i++) { + Field one = fields.get(i); + try { + CstFieldRef field = new CstFieldRef(thisClass, one.getNat()); + int accessFlags = one.getAccessFlags(); + + if (AccessFlags.isStatic(accessFlags)) { + TypedConstant constVal = one.getConstantValue(); + EncodedField fi = new EncodedField(field, accessFlags); + if (constVal != null) { + constVal = coerceConstant(constVal, field.getType()); + } + out.addStaticField(fi, constVal); + } else { + EncodedField fi = new EncodedField(field, accessFlags); + out.addInstanceField(fi); + } + + Annotations annotations = + AttributeTranslator.getAnnotations(one.getAttributes()); + if (annotations.size() != 0) { + out.addFieldAnnotations(field, annotations, dexFile); + } + dexFile.getFieldIds().intern(field); + } catch (RuntimeException ex) { + String msg = "...while processing " + one.getName().toHuman() + + " " + one.getDescriptor().toHuman(); + throw ExceptionWithContext.withContext(ex, msg); + } + } + } + + /** + * Helper for {@link #processFields}, which translates constants into + * more specific types if necessary. + * + * @param constant {@code non-null;} the constant in question + * @param type {@code non-null;} the desired type + */ + private static TypedConstant coerceConstant(TypedConstant constant, + Type type) { + Type constantType = constant.getType(); + + if (constantType.equals(type)) { + return constant; + } + + switch (type.getBasicType()) { + case Type.BT_BOOLEAN: { + return CstBoolean.make(((CstInteger) constant).getValue()); + } + case Type.BT_BYTE: { + return CstByte.make(((CstInteger) constant).getValue()); + } + case Type.BT_CHAR: { + return CstChar.make(((CstInteger) constant).getValue()); + } + case Type.BT_SHORT: { + return CstShort.make(((CstInteger) constant).getValue()); + } + default: { + throw new UnsupportedOperationException("can't coerce " + + constant + " to " + type); + } + } + } + + /** + * Processes the methods of the given class. + * + * @param context {@code non-null;} the state global to this invocation. + * @param cf {@code non-null;} class being translated + * @param cfOptions {@code non-null;} options for class translation + * @param dexOptions {@code non-null;} options for dex output + * @param out {@code non-null;} output class + * @param dexFile {@code non-null;} dex output + */ + private static void processMethods(DxContext context, DirectClassFile cf, CfOptions cfOptions, + DexOptions dexOptions, ClassDefItem out, DexFile dexFile) { + CstType thisClass = cf.getThisClass(); + MethodList methods = cf.getMethods(); + int sz = methods.size(); + + for (int i = 0; i < sz; i++) { + Method one = methods.get(i); + try { + CstMethodRef meth = new CstMethodRef(thisClass, one.getNat()); + int accessFlags = one.getAccessFlags(); + boolean isStatic = AccessFlags.isStatic(accessFlags); + boolean isPrivate = AccessFlags.isPrivate(accessFlags); + boolean isNative = AccessFlags.isNative(accessFlags); + boolean isAbstract = AccessFlags.isAbstract(accessFlags); + boolean isConstructor = meth.isInstanceInit() || + meth.isClassInit(); + DalvCode code; + + if (isNative || isAbstract) { + // There's no code for native or abstract methods. + code = null; + } else { + ConcreteMethod concrete = + new ConcreteMethod(one, cf, + (cfOptions.positionInfo != PositionList.NONE), + cfOptions.localInfo); + + TranslationAdvice advice; + + advice = DexTranslationAdvice.THE_ONE; + + RopMethod rmeth = Ropper.convert(concrete, advice, methods, dexOptions); + RopMethod nonOptRmeth = null; + int paramSize; + + paramSize = meth.getParameterWordCount(isStatic); + + String canonicalName + = thisClass.getClassType().getDescriptor() + + "." + one.getName().getString(); + + if (cfOptions.optimize && + context.optimizerOptions.shouldOptimize(canonicalName)) { + if (DEBUG) { + System.err.println("Optimizing " + canonicalName); + } + + nonOptRmeth = rmeth; + rmeth = Optimizer.optimize(rmeth, + paramSize, isStatic, cfOptions.localInfo, advice); + + if (DEBUG) { + context.optimizerOptions.compareOptimizerStep(nonOptRmeth, + paramSize, isStatic, cfOptions, advice, rmeth); + } + + if (cfOptions.statistics) { + context.codeStatistics.updateRopStatistics( + nonOptRmeth, rmeth); + } + } + + LocalVariableInfo locals = null; + + if (cfOptions.localInfo) { + locals = LocalVariableExtractor.extract(rmeth); + } + + code = RopTranslator.translate(rmeth, cfOptions.positionInfo, + locals, paramSize, dexOptions); + + if (cfOptions.statistics && nonOptRmeth != null) { + updateDexStatistics(context, cfOptions, dexOptions, rmeth, nonOptRmeth, locals, + paramSize, concrete.getCode().size()); + } + } + + // Preserve the synchronized flag as its "declared" variant... + if (AccessFlags.isSynchronized(accessFlags)) { + accessFlags |= AccessFlags.ACC_DECLARED_SYNCHRONIZED; + + /* + * ...but only native methods are actually allowed to be + * synchronized. + */ + if (!isNative) { + accessFlags &= ~AccessFlags.ACC_SYNCHRONIZED; + } + } + + if (isConstructor) { + accessFlags |= AccessFlags.ACC_CONSTRUCTOR; + } + + TypeList exceptions = AttributeTranslator.getExceptions(one); + EncodedMethod mi = + new EncodedMethod(meth, accessFlags, code, exceptions); + + if (meth.isInstanceInit() || meth.isClassInit() || + isStatic || isPrivate) { + out.addDirectMethod(mi); + } else { + out.addVirtualMethod(mi); + } + + Annotations annotations = + AttributeTranslator.getMethodAnnotations(one); + if (annotations.size() != 0) { + out.addMethodAnnotations(meth, annotations, dexFile); + } + + AnnotationsList list = + AttributeTranslator.getParameterAnnotations(one); + if (list.size() != 0) { + out.addParameterAnnotations(meth, list, dexFile); + } + dexFile.getMethodIds().intern(meth); + } catch (RuntimeException ex) { + String msg = "...while processing " + one.getName().toHuman() + + " " + one.getDescriptor().toHuman(); + throw ExceptionWithContext.withContext(ex, msg); + } + } + } + + /** + * Helper that updates the dex statistics. + */ + private static void updateDexStatistics(DxContext context, CfOptions cfOptions, DexOptions dexOptions, + RopMethod optRmeth, RopMethod nonOptRmeth, + LocalVariableInfo locals, int paramSize, int originalByteCount) { + /* + * Run rop->dex again on optimized vs. non-optimized method to + * collect statistics. We have to totally convert both ways, + * since converting the "real" method getting added to the + * file would corrupt it (by messing with its constant pool + * indices). + */ + + DalvCode optCode = RopTranslator.translate(optRmeth, + cfOptions.positionInfo, locals, paramSize, dexOptions); + DalvCode nonOptCode = RopTranslator.translate(nonOptRmeth, + cfOptions.positionInfo, locals, paramSize, dexOptions); + + /* + * Fake out the indices, so code.getInsns() can work well enough + * for the current purpose. + */ + + DalvCode.AssignIndicesCallback callback = + new DalvCode.AssignIndicesCallback() { + public int getIndex(Constant cst) { + // Everything is at index 0! + return 0; + } + }; + + optCode.assignIndices(callback); + nonOptCode.assignIndices(callback); + + context.codeStatistics.updateDexStatistics(nonOptCode, optCode); + context.codeStatistics.updateOriginalByteCount(originalByteCount); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/cf/CodeStatistics.java b/dexlib/src/main/java/com/android/dx/dex/cf/CodeStatistics.java new file mode 100644 index 000000000..d0c75d987 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/cf/CodeStatistics.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.cf; + +import com.android.dx.dex.code.DalvCode; +import com.android.dx.rop.code.RopMethod; +import java.io.PrintStream; + +/** + * Static methods and variables for collecting statistics on generated + * code. + */ +public final class CodeStatistics { + /** set to {@code true} to enable development-time debugging code */ + private static final boolean DEBUG = false; + + /** + * running sum of the number of registers added/removed in + * SSA form by the optimizer + */ + public int runningDeltaRegisters = 0; + + /** + * running sum of the number of insns added/removed in + * SSA form by the optimizer + */ + public int runningDeltaInsns = 0; + + /** running sum of the total number of Rop insns processed */ + public int runningTotalInsns = 0; + + /** + * running sum of the number of dex-form registers added/removed in + * SSA form by the optimizer. Only valid if args.statistics is true. + */ + public int dexRunningDeltaRegisters = 0; + + /** + * running sum of the number of dex-form insns (actually code + * units) added/removed in SSA form by the optimizer. Only valid + * if args.statistics is true. + */ + public int dexRunningDeltaInsns = 0; + + /** + * running sum of the total number of dex insns (actually code + * units) processed + */ + public int dexRunningTotalInsns = 0; + + /** running sum of original class bytecode bytes */ + public int runningOriginalBytes = 0; + + /** + * Updates the number of original bytecode bytes processed. + * + * @param count {@code >= 0;} the number of bytes to add + */ + public void updateOriginalByteCount(int count) { + runningOriginalBytes += count; + } + + /** + * Updates the dex statistics. + * + * @param nonOptCode non-optimized code block + * @param code optimized code block + */ + public void updateDexStatistics(DalvCode nonOptCode, + DalvCode code) { + if (DEBUG) { + System.err.println("dex insns (old/new) " + + nonOptCode.getInsns().codeSize() + + "/" + code.getInsns().codeSize() + + " regs (o/n) " + + nonOptCode.getInsns().getRegistersSize() + + "/" + code.getInsns().getRegistersSize() + ); + } + + dexRunningDeltaInsns + += (code.getInsns().codeSize() + - nonOptCode.getInsns().codeSize()); + + dexRunningDeltaRegisters + += (code.getInsns().getRegistersSize() + - nonOptCode.getInsns().getRegistersSize()); + + dexRunningTotalInsns += code.getInsns().codeSize(); + } + + /** + * Updates the ROP statistics. + * + * @param nonOptRmeth non-optimized method + * @param rmeth optimized method + */ + public void updateRopStatistics(RopMethod nonOptRmeth, + RopMethod rmeth) { + int oldCountInsns + = nonOptRmeth.getBlocks().getEffectiveInstructionCount(); + int oldCountRegs = nonOptRmeth.getBlocks().getRegCount(); + + if (DEBUG) { + System.err.println("insns (old/new): " + + oldCountInsns + "/" + + rmeth.getBlocks().getEffectiveInstructionCount() + + " regs (o/n):" + oldCountRegs + + "/" + rmeth.getBlocks().getRegCount()); + } + + int newCountInsns + = rmeth.getBlocks().getEffectiveInstructionCount(); + + runningDeltaInsns + += (newCountInsns - oldCountInsns); + + runningDeltaRegisters + += (rmeth.getBlocks().getRegCount() - oldCountRegs); + + runningTotalInsns += newCountInsns; + } + + /** + * Prints out the collected statistics. + * + * @param out {@code non-null;} where to output to + */ + public void dumpStatistics(PrintStream out) { + out.printf("Optimizer Delta Rop Insns: %d total: %d " + + "(%.2f%%) Delta Registers: %d\n", + runningDeltaInsns, + runningTotalInsns, + (100.0 * (((float) runningDeltaInsns) + / (runningTotalInsns + Math.abs(runningDeltaInsns)))), + runningDeltaRegisters); + + out.printf("Optimizer Delta Dex Insns: Insns: %d total: %d " + + "(%.2f%%) Delta Registers: %d\n", + dexRunningDeltaInsns, + dexRunningTotalInsns, + (100.0 * (((float) dexRunningDeltaInsns) + / (dexRunningTotalInsns + + Math.abs(dexRunningDeltaInsns)))), + dexRunningDeltaRegisters); + + out.printf("Original bytecode byte count: %d\n", + runningOriginalBytes); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/cf/OptimizerOptions.java b/dexlib/src/main/java/com/android/dx/dex/cf/OptimizerOptions.java new file mode 100644 index 000000000..8132adf1b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/cf/OptimizerOptions.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.cf; + +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.TranslationAdvice; +import com.android.dx.ssa.Optimizer; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashSet; + +/** + * Settings for optimization of code. + */ +public class OptimizerOptions { + /** + * {@code null-ok;} hash set of class name + method names that + * should be optimized. {@code null} if this constraint was not + * specified on the command line + */ + private HashSet optimizeList; + + /** + * {@code null-ok;} hash set of class name + method names that should NOT + * be optimized. null if this constraint was not specified on the + * command line + */ + private HashSet dontOptimizeList; + + /** true if the above lists have been loaded */ + private boolean optimizeListsLoaded; + + + /** + * Loads the optimize/don't optimize lists from files. + * + * @param optimizeListFile Pathname + * @param dontOptimizeListFile Pathname + */ + public void loadOptimizeLists(String optimizeListFile, + String dontOptimizeListFile) { + if (optimizeListsLoaded) { + return; + } + + if (optimizeListFile != null && dontOptimizeListFile != null) { + /* + * We shouldn't get this far. The condition should have + * been caught in the arg processor. + */ + throw new RuntimeException("optimize and don't optimize lists " + + " are mutually exclusive."); + } + + if (optimizeListFile != null) { + optimizeList = loadStringsFromFile(optimizeListFile); + } + + if (dontOptimizeListFile != null) { + dontOptimizeList = loadStringsFromFile(dontOptimizeListFile); + } + + optimizeListsLoaded = true; + } + + /** + * Loads a list of newline-separated strings into a new HashSet and returns + * the HashSet. + * + * @param filename filename to process + * @return set of all unique lines in the file + */ + private static HashSet loadStringsFromFile(String filename) { + HashSet result = new HashSet(); + + try { + FileReader fr = new FileReader(filename); + BufferedReader bfr = new BufferedReader(fr); + + String line; + + while (null != (line = bfr.readLine())) { + result.add(line); + } + + fr.close(); + } catch (IOException ex) { + // Let the exception percolate up as a RuntimeException. + throw new RuntimeException("Error with optimize list: " + + filename, ex); + } + + return result; + } + + /** + * Compares the output of the optimizer run normally with a run skipping + * some optional steps. Results are printed to stderr. + * + * @param nonOptRmeth {@code non-null;} origional rop method + * @param paramSize {@code >= 0;} parameter size of method + * @param isStatic true if this method has no 'this' pointer argument. + * @param args {@code non-null;} translator arguments + * @param advice {@code non-null;} translation advice + * @param rmeth {@code non-null;} method with all optimization steps run. + */ + public void compareOptimizerStep(RopMethod nonOptRmeth, + int paramSize, boolean isStatic, CfOptions args, + TranslationAdvice advice, RopMethod rmeth) { + EnumSet steps; + + steps = EnumSet.allOf(Optimizer.OptionalStep.class); + + // This is the step to skip. + steps.remove(Optimizer.OptionalStep.CONST_COLLECTOR); + + RopMethod skipRopMethod + = Optimizer.optimize(nonOptRmeth, + paramSize, isStatic, args.localInfo, advice, steps); + + int normalInsns + = rmeth.getBlocks().getEffectiveInstructionCount(); + int skipInsns + = skipRopMethod.getBlocks().getEffectiveInstructionCount(); + + System.err.printf( + "optimize step regs:(%d/%d/%.2f%%)" + + " insns:(%d/%d/%.2f%%)\n", + rmeth.getBlocks().getRegCount(), + skipRopMethod.getBlocks().getRegCount(), + 100.0 * ((skipRopMethod.getBlocks().getRegCount() + - rmeth.getBlocks().getRegCount()) + / (float) skipRopMethod.getBlocks().getRegCount()), + normalInsns, skipInsns, + 100.0 * ((skipInsns - normalInsns) / (float) skipInsns)); + } + + /** + * Checks whether the specified method should be optimized + * + * @param canonicalMethodName name of method being considered + * @return true if it should be optimized + */ + public boolean shouldOptimize(String canonicalMethodName) { + // Optimize only what's in the optimize list. + if (optimizeList != null) { + return optimizeList.contains(canonicalMethodName); + } + + /* + * Or don't optimize what's listed here. (The two lists are + * mutually exclusive. + */ + + if (dontOptimizeList != null) { + return !dontOptimizeList.contains(canonicalMethodName); + } + + // If neither list has been specified, then optimize everything. + return true; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/cf/package.html b/dexlib/src/main/java/com/android/dx/dex/cf/package.html new file mode 100644 index 000000000..d56e8a759 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/cf/package.html @@ -0,0 +1,15 @@ + +

Classes for translating Java classfiles into Dalvik classes.

+ +

PACKAGES USED: +

    +
  • com.android.dx.cf.code
  • +
  • com.android.dx.cf.direct
  • +
  • com.android.dx.cf.iface
  • +
  • com.android.dx.dex.code
  • +
  • com.android.dx.dex.file
  • +
  • com.android.dx.rop.code
  • +
  • com.android.dx.rop.cst
  • +
  • com.android.dx.util
  • +
+ diff --git a/dexlib/src/main/java/com/android/dx/dex/code/ArrayData.java b/dexlib/src/main/java/com/android/dx/dex/code/ArrayData.java new file mode 100644 index 000000000..fd2871285 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/ArrayData.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.io.Opcodes; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteral32; +import com.android.dx.rop.cst.CstLiteral64; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.ArrayList; + +/** + * Pseudo-instruction which holds fill array data. + */ +public final class ArrayData extends VariableSizeInsn { + /** + * {@code non-null;} address representing the instruction that uses this + * instance + */ + private final CodeAddress user; + + /** {@code non-null;} initial values to be filled into an array */ + private final ArrayList values; + + /** non-null: type of constant that initializes the array */ + private final Constant arrayType; + + /** Width of the init value element */ + private final int elemWidth; + + /** Length of the init list */ + private final int initLength; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param user {@code non-null;} address representing the instruction that + * uses this instance + * @param values {@code non-null;} initial values to be filled into an array + */ + public ArrayData(SourcePosition position, CodeAddress user, + ArrayList values, + Constant arrayType) { + super(position, RegisterSpecList.EMPTY); + + if (user == null) { + throw new NullPointerException("user == null"); + } + + if (values == null) { + throw new NullPointerException("values == null"); + } + + int sz = values.size(); + + if (sz <= 0) { + throw new IllegalArgumentException("Illegal number of init values"); + } + + this.arrayType = arrayType; + + if (arrayType == CstType.BYTE_ARRAY || + arrayType == CstType.BOOLEAN_ARRAY) { + elemWidth = 1; + } else if (arrayType == CstType.SHORT_ARRAY || + arrayType == CstType.CHAR_ARRAY) { + elemWidth = 2; + } else if (arrayType == CstType.INT_ARRAY || + arrayType == CstType.FLOAT_ARRAY) { + elemWidth = 4; + } else if (arrayType == CstType.LONG_ARRAY || + arrayType == CstType.DOUBLE_ARRAY) { + elemWidth = 8; + } else { + throw new IllegalArgumentException("Unexpected constant type"); + } + this.user = user; + this.values = values; + initLength = values.size(); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + int sz = initLength; + // Note: the unit here is 16-bit + return 4 + ((sz * elemWidth) + 1) / 2; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + int sz = values.size(); + + out.writeShort(Opcodes.FILL_ARRAY_DATA_PAYLOAD); + out.writeShort(elemWidth); + out.writeInt(initLength); + + + // For speed reasons, replicate the for loop in each case + switch (elemWidth) { + case 1: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeByte((byte) ((CstLiteral32) cst).getIntBits()); + } + break; + } + case 2: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeShort((short) ((CstLiteral32) cst).getIntBits()); + } + break; + } + case 4: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeInt(((CstLiteral32) cst).getIntBits()); + } + break; + } + case 8: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeLong(((CstLiteral64) cst).getLongBits()); + } + break; + } + default: + break; + } + + // Pad one byte to make the size of data table multiples of 16-bits + if (elemWidth == 1 && (sz % 2 != 0)) { + out.writeByte(0x00); + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new ArrayData(getPosition(), user, values, arrayType); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + StringBuffer sb = new StringBuffer(100); + + int sz = values.size(); + for (int i = 0; i < sz; i++) { + sb.append("\n "); + sb.append(i); + sb.append(": "); + sb.append(values.get(i).toHuman()); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + int baseAddress = user.getAddress(); + StringBuffer sb = new StringBuffer(100); + int sz = values.size(); + + sb.append("fill-array-data-payload // for fill-array-data @ "); + sb.append(Hex.u2(baseAddress)); + + for (int i = 0; i < sz; i++) { + sb.append("\n "); + sb.append(i); + sb.append(": "); + sb.append(values.get(i).toHuman()); + } + + return sb.toString(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/BlockAddresses.java b/dexlib/src/main/java/com/android/dx/dex/code/BlockAddresses.java new file mode 100644 index 000000000..1a1d184ff --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/BlockAddresses.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.BasicBlock; +import com.android.dx.rop.code.BasicBlockList; +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.SourcePosition; + +/** + * Container for the set of {@link CodeAddress} instances associated with + * the blocks of a particular method. Each block has a corresponding + * start address, end address, and last instruction address. + */ +public final class BlockAddresses { + /** {@code non-null;} array containing addresses for the start of each basic + * block (indexed by basic block label) */ + private final CodeAddress[] starts; + + /** {@code non-null;} array containing addresses for the final instruction + * of each basic block (indexed by basic block label) */ + private final CodeAddress[] lasts; + + /** {@code non-null;} array containing addresses for the end (just past the + * final instruction) of each basic block (indexed by basic block + * label) */ + private final CodeAddress[] ends; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method to have block addresses for + */ + public BlockAddresses(RopMethod method) { + BasicBlockList blocks = method.getBlocks(); + int maxLabel = blocks.getMaxLabel(); + + this.starts = new CodeAddress[maxLabel]; + this.lasts = new CodeAddress[maxLabel]; + this.ends = new CodeAddress[maxLabel]; + + setupArrays(method); + } + + /** + * Gets the instance for the start of the given block. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getStart(BasicBlock block) { + return starts[block.getLabel()]; + } + + /** + * Gets the instance for the start of the block with the given label. + * + * @param label {@code non-null;} the label of the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getStart(int label) { + return starts[label]; + } + + /** + * Gets the instance for the final instruction of the given block. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getLast(BasicBlock block) { + return lasts[block.getLabel()]; + } + + /** + * Gets the instance for the final instruction of the block with + * the given label. + * + * @param label {@code non-null;} the label of the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getLast(int label) { + return lasts[label]; + } + + /** + * Gets the instance for the end (address after the final instruction) + * of the given block. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getEnd(BasicBlock block) { + return ends[block.getLabel()]; + } + + /** + * Gets the instance for the end (address after the final instruction) + * of the block with the given label. + * + * @param label {@code non-null;} the label of the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getEnd(int label) { + return ends[label]; + } + + /** + * Sets up the address arrays. + */ + private void setupArrays(RopMethod method) { + BasicBlockList blocks = method.getBlocks(); + int sz = blocks.size(); + + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + int label = one.getLabel(); + Insn insn = one.getInsns().get(0); + + starts[label] = new CodeAddress(insn.getPosition()); + + SourcePosition pos = one.getLastInsn().getPosition(); + + lasts[label] = new CodeAddress(pos); + ends[label] = new CodeAddress(pos); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/CatchBuilder.java b/dexlib/src/main/java/com/android/dx/dex/code/CatchBuilder.java new file mode 100644 index 000000000..059091e57 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/CatchBuilder.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.type.Type; +import java.util.HashSet; + +/** + * Interface for the construction of {@link CatchTable} instances. + */ +public interface CatchBuilder { + /** + * Builds and returns the catch table for this instance. + * + * @return {@code non-null;} the constructed table + */ + public CatchTable build(); + + /** + * Gets whether this instance has any catches at all (either typed + * or catch-all). + * + * @return whether this instance has any catches at all + */ + public boolean hasAnyCatches(); + + /** + * Gets the set of catch types associated with this instance. + * + * @return {@code non-null;} the set of catch types + */ + public HashSet getCatchTypes(); +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/CatchHandlerList.java b/dexlib/src/main/java/com/android/dx/dex/code/CatchHandlerList.java new file mode 100644 index 000000000..84725849d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/CatchHandlerList.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.FixedSizeList; +import com.android.dx.util.Hex; + +/** + * Ordered list of (exception type, handler address) entries. + */ +public final class CatchHandlerList extends FixedSizeList + implements Comparable { + /** {@code non-null;} empty instance */ + public static final CatchHandlerList EMPTY = new CatchHandlerList(0); + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the list + */ + public CatchHandlerList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toHuman("", ""); + } + + /** + * Get the human form of this instance, prefixed on each line + * with the string. + * + * @param prefix {@code non-null;} the prefix for every line + * @param header {@code non-null;} the header for the first line (after the + * first prefix) + * @return {@code non-null;} the human form + */ + public String toHuman(String prefix, String header) { + StringBuilder sb = new StringBuilder(100); + int size = size(); + + sb.append(prefix); + sb.append(header); + sb.append("catch "); + + for (int i = 0; i < size; i++) { + Entry entry = get(i); + + if (i != 0) { + sb.append(",\n"); + sb.append(prefix); + sb.append(" "); + } + + if ((i == (size - 1)) && catchesAll()) { + sb.append(""); + } else { + sb.append(entry.getExceptionType().toHuman()); + } + + sb.append(" -> "); + sb.append(Hex.u2or4(entry.getHandler())); + } + + return sb.toString(); + } + + /** + * Returns whether or not this instance ends with a "catch-all" + * handler. + * + * @return {@code true} if this instance ends with a "catch-all" + * handler or {@code false} if not + */ + public boolean catchesAll() { + int size = size(); + + if (size == 0) { + return false; + } + + Entry last = get(size - 1); + return last.getExceptionType().equals(CstType.OBJECT); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param exceptionType {@code non-null;} type of exception handled + * @param handler {@code >= 0;} exception handler address + */ + public void set(int n, CstType exceptionType, int handler) { + set0(n, new Entry(exceptionType, handler)); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** {@inheritDoc} */ + public int compareTo(CatchHandlerList other) { + if (this == other) { + // Easy out. + return 0; + } + + int thisSize = size(); + int otherSize = other.size(); + int checkSize = Math.min(thisSize, otherSize); + + for (int i = 0; i < checkSize; i++) { + Entry thisEntry = get(i); + Entry otherEntry = other.get(i); + int compare = thisEntry.compareTo(otherEntry); + if (compare != 0) { + return compare; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } + + return 0; + } + + /** + * Entry in the list. + */ + public static class Entry implements Comparable { + /** {@code non-null;} type of exception handled */ + private final CstType exceptionType; + + /** {@code >= 0;} exception handler address */ + private final int handler; + + /** + * Constructs an instance. + * + * @param exceptionType {@code non-null;} type of exception handled + * @param handler {@code >= 0;} exception handler address + */ + public Entry(CstType exceptionType, int handler) { + if (handler < 0) { + throw new IllegalArgumentException("handler < 0"); + } + + if (exceptionType == null) { + throw new NullPointerException("exceptionType == null"); + } + + this.handler = handler; + this.exceptionType = exceptionType; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (handler * 31) + exceptionType.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (other instanceof Entry) { + return (compareTo((Entry) other) == 0); + } + + return false; + } + + /** {@inheritDoc} */ + public int compareTo(Entry other) { + if (handler < other.handler) { + return -1; + } else if (handler > other.handler) { + return 1; + } + + return exceptionType.compareTo(other.exceptionType); + } + + /** + * Gets the exception type handled. + * + * @return {@code non-null;} the exception type + */ + public CstType getExceptionType() { + return exceptionType; + } + + /** + * Gets the handler address. + * + * @return {@code >= 0;} the handler address + */ + public int getHandler() { + return handler; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/CatchTable.java b/dexlib/src/main/java/com/android/dx/dex/code/CatchTable.java new file mode 100644 index 000000000..8470789fe --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/CatchTable.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.util.FixedSizeList; + +/** + * Table of catch entries. Each entry includes a range of code + * addresses for which it is valid and an associated {@link + * CatchHandlerList}. + */ +public final class CatchTable extends FixedSizeList + implements Comparable { + /** {@code non-null;} empty instance */ + public static final CatchTable EMPTY = new CatchTable(0); + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the table + */ + public CatchTable(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** {@inheritDoc} */ + public int compareTo(CatchTable other) { + if (this == other) { + // Easy out. + return 0; + } + + int thisSize = size(); + int otherSize = other.size(); + int checkSize = Math.min(thisSize, otherSize); + + for (int i = 0; i < checkSize; i++) { + Entry thisEntry = get(i); + Entry otherEntry = other.get(i); + int compare = thisEntry.compareTo(otherEntry); + if (compare != 0) { + return compare; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } + + return 0; + } + + /** + * Entry in a catch list. + */ + public static class Entry implements Comparable { + /** {@code >= 0;} start address */ + private final int start; + + /** {@code > start;} end address (exclusive) */ + private final int end; + + /** {@code non-null;} list of catch handlers */ + private final CatchHandlerList handlers; + + /** + * Constructs an instance. + * + * @param start {@code >= 0;} start address + * @param end {@code > start;} end address (exclusive) + * @param handlers {@code non-null;} list of catch handlers + */ + public Entry(int start, int end, CatchHandlerList handlers) { + if (start < 0) { + throw new IllegalArgumentException("start < 0"); + } + + if (end <= start) { + throw new IllegalArgumentException("end <= start"); + } + + if (handlers.isMutable()) { + throw new IllegalArgumentException("handlers.isMutable()"); + } + + this.start = start; + this.end = end; + this.handlers = handlers; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int hash = (start * 31) + end; + hash = (hash * 31) + handlers.hashCode(); + return hash; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (other instanceof Entry) { + return (compareTo((Entry) other) == 0); + } + + return false; + } + + /** {@inheritDoc} */ + public int compareTo(Entry other) { + if (start < other.start) { + return -1; + } else if (start > other.start) { + return 1; + } + + if (end < other.end) { + return -1; + } else if (end > other.end) { + return 1; + } + + return handlers.compareTo(other.handlers); + } + + /** + * Gets the start address. + * + * @return {@code >= 0;} the start address + */ + public int getStart() { + return start; + } + + /** + * Gets the end address (exclusive). + * + * @return {@code > start;} the end address (exclusive) + */ + public int getEnd() { + return end; + } + + /** + * Gets the handlers. + * + * @return {@code non-null;} the handlers + */ + public CatchHandlerList getHandlers() { + return handlers; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/CodeAddress.java b/dexlib/src/main/java/com/android/dx/dex/code/CodeAddress.java new file mode 100644 index 000000000..b31e31cc5 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/CodeAddress.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; + +/** + * Pseudo-instruction which is used to track an address within a code + * array. Instances are used for such things as branch targets and + * exception handler ranges. Its code size is zero, and so instances + * do not in general directly wind up in any output (either + * human-oriented or binary file). + */ +public final class CodeAddress extends ZeroSizeInsn { + /** If this address should bind closely to the following real instruction */ + private final boolean bindsClosely; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + */ + public CodeAddress(SourcePosition position) { + this(position, false); + } + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param bindsClosely if the address should bind closely to the following + * real instruction. + */ + public CodeAddress(SourcePosition position, boolean bindsClosely) { + super(position); + this.bindsClosely = bindsClosely; + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withRegisters(RegisterSpecList registers) { + return new CodeAddress(getPosition()); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + return "code-address"; + } + + /** + * Gets whether this address binds closely to the following "real" + * (non-zero-length) instruction. + * + * When a prefix is added to an instruction (for example, to move a value + * from a high register to a low register), this determines whether this + * {@code CodeAddress} will point to the prefix, or to the instruction + * itself. + * + * If bindsClosely is true, the address will point to the instruction + * itself, otherwise it will point to the prefix (if any) + * + * @return true if this address binds closely to the next real instruction + */ + public boolean getBindsClosely() { + return bindsClosely; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/CstInsn.java b/dexlib/src/main/java/com/android/dx/dex/code/CstInsn.java new file mode 100644 index 000000000..d4e5223b6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/CstInsn.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.Hex; + +/** + * Instruction which has a single constant argument in addition + * to all the normal instruction information. + */ +public final class CstInsn extends FixedSizeInsn { + /** {@code non-null;} the constant argument for this instruction */ + private final Constant constant; + + /** + * {@code >= -1;} the constant pool index for {@link #constant}, or + * {@code -1} if not yet set + */ + private int index; + + /** + * {@code >= -1;} the constant pool index for the class reference in + * {@link #constant} if any, or {@code -1} if not yet set + */ + private int classIndex; + + /** + * Constructs an instance. The output address of this instance is + * initially unknown ({@code -1}) as is the constant pool index. + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + * @param constant {@code non-null;} constant argument + */ + public CstInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers, Constant constant) { + super(opcode, position, registers); + + if (constant == null) { + throw new NullPointerException("constant == null"); + } + + this.constant = constant; + this.index = -1; + this.classIndex = -1; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + CstInsn result = + new CstInsn(opcode, getPosition(), getRegisters(), constant); + + if (index >= 0) { + result.setIndex(index); + } + + if (classIndex >= 0) { + result.setClassIndex(classIndex); + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + CstInsn result = + new CstInsn(getOpcode(), getPosition(), registers, constant); + + if (index >= 0) { + result.setIndex(index); + } + + if (classIndex >= 0) { + result.setClassIndex(classIndex); + } + + return result; + } + + /** + * Gets the constant argument. + * + * @return {@code non-null;} the constant argument + */ + public Constant getConstant() { + return constant; + } + + /** + * Gets the constant's index. It is only valid to call this after + * {@link #setIndex} has been called. + * + * @return {@code >= 0;} the constant pool index + */ + public int getIndex() { + if (index < 0) { + throw new IllegalStateException("index not yet set for " + constant); + } + + return index; + } + + /** + * Returns whether the constant's index has been set for this instance. + * + * @see #setIndex + * + * @return {@code true} iff the index has been set + */ + public boolean hasIndex() { + return (index >= 0); + } + + /** + * Sets the constant's index. It is only valid to call this method once + * per instance. + * + * @param index {@code index >= 0;} the constant pool index + */ + public void setIndex(int index) { + if (index < 0) { + throw new IllegalArgumentException("index < 0"); + } + + if (this.index >= 0) { + throw new IllegalStateException("index already set"); + } + + this.index = index; + } + + /** + * Gets the constant's class index. It is only valid to call this after + * {@link #setClassIndex} has been called. + * + * @return {@code >= 0;} the constant's class's constant pool index + */ + public int getClassIndex() { + if (classIndex < 0) { + throw new IllegalStateException("class index not yet set"); + } + + return classIndex; + } + + /** + * Returns whether the constant's class index has been set for this + * instance. + * + * @see #setClassIndex + * + * @return {@code true} iff the index has been set + */ + public boolean hasClassIndex() { + return (classIndex >= 0); + } + + /** + * Sets the constant's class index. This is the constant pool index + * for the class referred to by this instance's constant. Only + * reference constants have a class, so it is only on instances + * with reference constants that this method should ever be + * called. It is only valid to call this method once per instance. + * + * @param index {@code index >= 0;} the constant's class's constant pool index + */ + public void setClassIndex(int index) { + if (index < 0) { + throw new IllegalArgumentException("index < 0"); + } + + if (this.classIndex >= 0) { + throw new IllegalStateException("class index already set"); + } + + this.classIndex = index; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return constant.toHuman(); + } + + /** {@inheritDoc} */ + @Override + public String cstString() { + if (constant instanceof CstString) { + return ((CstString) constant).toQuoted(); + } + return constant.toHuman(); + } + + /** {@inheritDoc} */ + @Override + public String cstComment() { + if (!hasIndex()) { + return ""; + } + + StringBuilder sb = new StringBuilder(20); + sb.append(getConstant().typeName()); + sb.append('@'); + + if (index < 65536) { + sb.append(Hex.u2(index)); + } else { + sb.append(Hex.u4(index)); + } + + return sb.toString(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/DalvCode.java b/dexlib/src/main/java/com/android/dx/dex/code/DalvCode.java new file mode 100644 index 000000000..ebaf28857 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/DalvCode.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.type.Type; +import java.util.HashSet; + +/** + * Container for all the pieces of a concrete method. Each instance + * corresponds to a {@code code} structure in a {@code .dex} file. + */ +public final class DalvCode { + /** + * how much position info to preserve; one of the static + * constants in {@link PositionList} + */ + private final int positionInfo; + + /** + * {@code null-ok;} the instruction list, ready for final processing; + * nulled out in {@link #finishProcessingIfNecessary} + */ + private OutputFinisher unprocessedInsns; + + /** + * {@code non-null;} unprocessed catch table; + * nulled out in {@link #finishProcessingIfNecessary} + */ + private CatchBuilder unprocessedCatches; + + /** + * {@code null-ok;} catch table; set in + * {@link #finishProcessingIfNecessary} + */ + private CatchTable catches; + + /** + * {@code null-ok;} source positions list; set in + * {@link #finishProcessingIfNecessary} + */ + private PositionList positions; + + /** + * {@code null-ok;} local variable list; set in + * {@link #finishProcessingIfNecessary} + */ + private LocalList locals; + + /** + * {@code null-ok;} the processed instruction list; set in + * {@link #finishProcessingIfNecessary} + */ + private DalvInsnList insns; + + /** + * Constructs an instance. + * + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param unprocessedInsns {@code non-null;} the instruction list, ready + * for final processing + * @param unprocessedCatches {@code non-null;} unprocessed catch + * (exception handler) table + */ + public DalvCode(int positionInfo, OutputFinisher unprocessedInsns, + CatchBuilder unprocessedCatches) { + if (unprocessedInsns == null) { + throw new NullPointerException("unprocessedInsns == null"); + } + + if (unprocessedCatches == null) { + throw new NullPointerException("unprocessedCatches == null"); + } + + this.positionInfo = positionInfo; + this.unprocessedInsns = unprocessedInsns; + this.unprocessedCatches = unprocessedCatches; + this.catches = null; + this.positions = null; + this.locals = null; + this.insns = null; + } + + /** + * Finish up processing of the method. + */ + private void finishProcessingIfNecessary() { + if (insns != null) { + return; + } + + insns = unprocessedInsns.finishProcessingAndGetList(); + positions = PositionList.make(insns, positionInfo); + locals = LocalList.make(insns); + catches = unprocessedCatches.build(); + + // Let them be gc'ed. + unprocessedInsns = null; + unprocessedCatches = null; + } + + /** + * Assign indices in all instructions that need them, using the + * given callback to perform lookups. This must be called before + * {@link #getInsns}. + * + * @param callback {@code non-null;} callback object + */ + public void assignIndices(AssignIndicesCallback callback) { + unprocessedInsns.assignIndices(callback); + } + + /** + * Gets whether this instance has any position data to represent. + * + * @return {@code true} iff this instance has any position + * data to represent + */ + public boolean hasPositions() { + return (positionInfo != PositionList.NONE) + && unprocessedInsns.hasAnyPositionInfo(); + } + + /** + * Gets whether this instance has any local variable data to represent. + * + * @return {@code true} iff this instance has any local variable + * data to represent + */ + public boolean hasLocals() { + return unprocessedInsns.hasAnyLocalInfo(); + } + + /** + * Gets whether this instance has any catches at all (either typed + * or catch-all). + * + * @return whether this instance has any catches at all + */ + public boolean hasAnyCatches() { + return unprocessedCatches.hasAnyCatches(); + } + + /** + * Gets the set of catch types handled anywhere in the code. + * + * @return {@code non-null;} the set of catch types + */ + public HashSet getCatchTypes() { + return unprocessedCatches.getCatchTypes(); + } + + /** + * Gets the set of all constants referred to by instructions in + * the code. + * + * @return {@code non-null;} the set of constants + */ + public HashSet getInsnConstants() { + return unprocessedInsns.getAllConstants(); + } + + /** + * Gets the list of instructions. + * + * @return {@code non-null;} the instruction list + */ + public DalvInsnList getInsns() { + finishProcessingIfNecessary(); + return insns; + } + + /** + * Gets the catch (exception handler) table. + * + * @return {@code non-null;} the catch table + */ + public CatchTable getCatches() { + finishProcessingIfNecessary(); + return catches; + } + + /** + * Gets the source positions list. + * + * @return {@code non-null;} the source positions list + */ + public PositionList getPositions() { + finishProcessingIfNecessary(); + return positions; + } + + /** + * Gets the source positions list. + * + * @return {@code non-null;} the source positions list + */ + public LocalList getLocals() { + finishProcessingIfNecessary(); + return locals; + } + + /** + * Class used as a callback for {@link #assignIndices}. + */ + public static interface AssignIndicesCallback { + /** + * Gets the index for the given constant. + * + * @param cst {@code non-null;} the constant + * @return {@code >= -1;} the index or {@code -1} if the constant + * shouldn't actually be reified with an index + */ + public int getIndex(Constant cst); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/DalvInsn.java b/dexlib/src/main/java/com/android/dx/dex/code/DalvInsn.java new file mode 100644 index 000000000..1bbfea87b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/DalvInsn.java @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.ssa.RegisterMapper; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import com.android.dx.util.TwoColumnOutput; + +import java.util.BitSet; + +/** + * Base class for Dalvik instructions. + */ +public abstract class DalvInsn { + /** + * the actual output address of this instance, if known, or + * {@code -1} if not + */ + private int address; + + /** the opcode; one of the constants from {@link Dops} */ + private final Dop opcode; + + /** {@code non-null;} source position */ + private final SourcePosition position; + + /** {@code non-null;} list of register arguments */ + private final RegisterSpecList registers; + + /** + * Makes a move instruction, appropriate and ideal for the given arguments. + * + * @param position {@code non-null;} source position information + * @param dest {@code non-null;} destination register + * @param src {@code non-null;} source register + * @return {@code non-null;} an appropriately-constructed instance + */ + public static SimpleInsn makeMove(SourcePosition position, + RegisterSpec dest, RegisterSpec src) { + boolean category1 = dest.getCategory() == 1; + boolean reference = dest.getType().isReference(); + int destReg = dest.getReg(); + int srcReg = src.getReg(); + Dop opcode; + + if ((srcReg | destReg) < 16) { + opcode = reference ? Dops.MOVE_OBJECT : + (category1 ? Dops.MOVE : Dops.MOVE_WIDE); + } else if (destReg < 256) { + opcode = reference ? Dops.MOVE_OBJECT_FROM16 : + (category1 ? Dops.MOVE_FROM16 : Dops.MOVE_WIDE_FROM16); + } else { + opcode = reference ? Dops.MOVE_OBJECT_16 : + (category1 ? Dops.MOVE_16 : Dops.MOVE_WIDE_16); + } + + return new SimpleInsn(opcode, position, + RegisterSpecList.make(dest, src)); + } + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + *

Note: In the unlikely event that an instruction takes + * absolutely no registers (e.g., a {@code nop} or a + * no-argument no-result static method call), then the given + * register list may be passed as {@link + * RegisterSpecList#EMPTY}.

+ * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins and outs) + */ + public DalvInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers) { + if (opcode == null) { + throw new NullPointerException("opcode == null"); + } + + if (position == null) { + throw new NullPointerException("position == null"); + } + + if (registers == null) { + throw new NullPointerException("registers == null"); + } + + this.address = -1; + this.opcode = opcode; + this.position = position; + this.registers = registers; + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(identifierString()); + sb.append(' '); + sb.append(position); + + sb.append(": "); + sb.append(opcode.getName()); + + boolean needComma = false; + if (registers.size() != 0) { + sb.append(registers.toHuman(" ", ", ", null)); + needComma = true; + } + + String extra = argString(); + if (extra != null) { + if (needComma) { + sb.append(','); + } + sb.append(' '); + sb.append(extra); + } + + return sb.toString(); + } + + /** + * Gets whether the address of this instruction is known. + * + * @see #getAddress + * @see #setAddress + */ + public final boolean hasAddress() { + return (address >= 0); + } + + /** + * Gets the output address of this instruction, if it is known. This throws + * a {@code RuntimeException} if it has not yet been set. + * + * @see #setAddress + * + * @return {@code >= 0;} the output address + */ + public final int getAddress() { + if (address < 0) { + throw new RuntimeException("address not yet known"); + } + + return address; + } + + /** + * Gets the opcode. + * + * @return {@code non-null;} the opcode + */ + public final Dop getOpcode() { + return opcode; + } + + /** + * Gets the source position. + * + * @return {@code non-null;} the source position + */ + public final SourcePosition getPosition() { + return position; + } + + /** + * Gets the register list for this instruction. + * + * @return {@code non-null;} the registers + */ + public final RegisterSpecList getRegisters() { + return registers; + } + + /** + * Returns whether this instance's opcode uses a result register. + * This method is a convenient shorthand for + * {@code getOpcode().hasResult()}. + * + * @return {@code true} iff this opcode uses a result register + */ + public final boolean hasResult() { + return opcode.hasResult(); + } + + /** + * Gets the minimum distinct registers required for this instruction. + * Uses the given BitSet to determine which registers require + * replacement, and ignores registers that are already compatible. + * This assumes that the result (if any) can share registers with the + * sources (if any), that each source register is unique, and that + * (to be explicit here) category-2 values take up two consecutive + * registers. + * + * @param compatRegs {@code non-null;} set of compatible registers + * @return {@code >= 0;} the minimum distinct register requirement + */ + public final int getMinimumRegisterRequirement(BitSet compatRegs) { + boolean hasResult = hasResult(); + int regSz = registers.size(); + int resultRequirement = 0; + int sourceRequirement = 0; + + if (hasResult && !compatRegs.get(0)) { + resultRequirement = registers.get(0).getCategory(); + } + + for (int i = hasResult ? 1 : 0; i < regSz; i++) { + if (!compatRegs.get(i)) { + sourceRequirement += registers.get(i).getCategory(); + } + } + + return Math.max(sourceRequirement, resultRequirement); + } + + /** + * Gets the instruction that is equivalent to this one, except that + * it uses sequential registers starting at {@code 0} (storing + * the result, if any, in register {@code 0} as well). + * + * @return {@code non-null;} the replacement + */ + public DalvInsn getLowRegVersion() { + RegisterSpecList regs = + registers.withExpandedRegisters(0, hasResult(), null); + return withRegisters(regs); + } + + /** + * Gets the instruction prefix required, if any, to use in an expanded + * version of this instance. Will not generate moves for registers + * marked compatible to the format by the given BitSet. + * + * @see #expandedVersion + * + * @param compatRegs {@code non-null;} set of compatible registers + * @return {@code null-ok;} the prefix, if any + */ + public DalvInsn expandedPrefix(BitSet compatRegs) { + RegisterSpecList regs = registers; + boolean firstBit = compatRegs.get(0); + + if (hasResult()) compatRegs.set(0); + + regs = regs.subset(compatRegs); + + if (hasResult()) compatRegs.set(0, firstBit); + + if (regs.size() == 0) return null; + + return new HighRegisterPrefix(position, regs); + } + + /** + * Gets the instruction suffix required, if any, to use in an expanded + * version of this instance. Will not generate a move for a register + * marked compatible to the format by the given BitSet. + * + * @see #expandedVersion + * + * @param compatRegs {@code non-null;} set of compatible registers + * @return {@code null-ok;} the suffix, if any + */ + public DalvInsn expandedSuffix(BitSet compatRegs) { + if (hasResult() && !compatRegs.get(0)) { + RegisterSpec r = registers.get(0); + return makeMove(position, r, r.withReg(0)); + } else { + return null; + } + } + + /** + * Gets the instruction that is equivalent to this one, except that + * it replaces incompatible registers with sequential registers + * starting at {@code 0} (storing the result, if any, in register + * {@code 0} as well). The sequence of instructions from + * {@link #expandedPrefix} and {@link #expandedSuffix} (if non-null) + * surrounding the result of a call to this method are the expanded + * transformation of this instance, and it is guaranteed that the + * number of low registers used will be the number returned by + * {@link #getMinimumRegisterRequirement}. + * + * @param compatRegs {@code non-null;} set of compatible registers + * @return {@code non-null;} the replacement + */ + public DalvInsn expandedVersion(BitSet compatRegs) { + RegisterSpecList regs = + registers.withExpandedRegisters(0, hasResult(), compatRegs); + return withRegisters(regs); + } + + /** + * Gets the short identifier for this instruction. This is its + * address, if assigned, or its identity hashcode if not. + * + * @return {@code non-null;} the identifier + */ + public final String identifierString() { + if (address != -1) { + return String.format("%04x", address); + } + + return Hex.u4(System.identityHashCode(this)); + } + + /** + * Returns the string form of this instance suitable for inclusion in + * a human-oriented listing dump. This method will return {@code null} + * if this instance should not appear in a listing. + * + * @param prefix {@code non-null;} prefix before the address; each follow-on + * line will be indented to match as well + * @param width {@code width >= 0;} the width of the output or {@code 0} for + * unlimited width + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code null-ok;} the string form or {@code null} if this + * instance should not appear in a listing + */ + public final String listingString(String prefix, int width, + boolean noteIndices) { + String insnPerSe = listingString0(noteIndices); + + if (insnPerSe == null) { + return null; + } + + String addr = prefix + identifierString() + ": "; + int w1 = addr.length(); + int w2 = (width == 0) ? insnPerSe.length() : (width - w1); + + return TwoColumnOutput.toString(addr, w1, "", insnPerSe, w2); + } + + /** + * Sets the output address. + * + * @param address {@code address >= 0;} the output address + */ + public final void setAddress(int address) { + if (address < 0) { + throw new IllegalArgumentException("address < 0"); + } + + this.address = address; + } + + /** + * Gets the address immediately after this instance. This is only + * calculable if this instance's address is known, and it is equal + * to the address plus the length of the instruction format of this + * instance's opcode. + * + * @return {@code >= 0;} the next address + */ + public final int getNextAddress() { + return getAddress() + codeSize(); + } + + /** + * Returns an instance that is just like this one, except that the + * register list is mapped by using {@code mapper}. + * + * @param mapper {@code non-null;} used to map registers + * @return {@code non-null;} an appropriately-constructed instance + */ + public DalvInsn withMapper(RegisterMapper mapper) { + return withRegisters(mapper.map(getRegisters())); + } + + /** + * Gets the size of this instruction, in 16-bit code units. + * + * @return {@code >= 0;} the code size of this instruction + */ + public abstract int codeSize(); + + /** + * Writes this instance to the given output. This method should + * never annotate the output. + * + * @param out {@code non-null;} where to write to + */ + public abstract void writeTo(AnnotatedOutput out); + + /** + * Returns an instance that is just like this one, except that its + * opcode is replaced by the one given, and its address is reset. + * + * @param opcode {@code non-null;} the new opcode + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract DalvInsn withOpcode(Dop opcode); + + /** + * Returns an instance that is just like this one, except that all + * register references have been offset by the given delta, and its + * address is reset. + * + * @param delta the amount to offset register references by + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract DalvInsn withRegisterOffset(int delta); + + /** + * Returns an instance that is just like this one, except that the + * register list is replaced by the given one, and its address is + * reset. + * + * @param registers {@code non-null;} new register list + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract DalvInsn withRegisters(RegisterSpecList registers); + + /** + * Gets the string form for any arguments to this instance. Subclasses + * must override this. + * + * @return {@code null-ok;} the string version of any arguments or + * {@code null} if there are none + */ + protected abstract String argString(); + + /** + * Helper for {@link #listingString}, which returns the string + * form of this instance suitable for inclusion in a + * human-oriented listing dump, not including the instruction + * address and without respect for any output formatting. This + * method should return {@code null} if this instance should + * not appear in a listing. + * + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code null-ok;} the listing string + */ + protected abstract String listingString0(boolean noteIndices); + + /** + * Helper which returns the string form of the associated constants + * for inclusion in a human oriented listing dump. + * + * This method is only implemented for instructions with one or more + * constants. + * + * @return the constant as a string. + */ + public String cstString() { + throw new UnsupportedOperationException("Not supported."); + } + + /** + * Helper which returns the comment form of the associated constants + * for inclusion in a human oriented listing dump. + * + * This method is only implemented for instructions with one or more + * constants. + * + * @return the comment as a string. + */ + public String cstComment() { + throw new UnsupportedOperationException("Not supported."); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/DalvInsnList.java b/dexlib/src/main/java/com/android/dx/dex/code/DalvInsnList.java new file mode 100644 index 000000000..ac8ba48f9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/DalvInsnList.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.io.Opcodes; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstBaseMethodRef; +import com.android.dx.rop.cst.CstProtoRef; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.FixedSizeList; +import com.android.dx.util.IndentingWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; + +/** + * List of {@link DalvInsn} instances. + */ +public final class DalvInsnList extends FixedSizeList { + + /** + * The amount of register space, in register units, required for this + * code block. This may be greater than the largest observed register+ + * category because the method this code block exists in may + * specify arguments that are unused by the method. + */ + private final int regCount; + + /** + * Constructs and returns an immutable instance whose elements are + * identical to the ones in the given list, in the same order. + * + * @param list {@code non-null;} the list to use for elements + * @param regCount count, in register-units, of the number of registers + * this code block requires. + * @return {@code non-null;} an appropriately-constructed instance of this + * class + */ + public static DalvInsnList makeImmutable(ArrayList list, + int regCount) { + int size = list.size(); + DalvInsnList result = new DalvInsnList(size, regCount); + + for (int i = 0; i < size; i++) { + result.set(i, list.get(i)); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + * @param regCount count, in register-units, of the number of registers + * this code block requires. + */ + public DalvInsnList(int size, int regCount) { + super(size); + this.regCount = regCount; + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public DalvInsn get(int n) { + return (DalvInsn) get0(n); + } + + /** + * Sets the instruction at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param insn {@code non-null;} the instruction to set at {@code n} + */ + public void set(int n, DalvInsn insn) { + set0(n, insn); + } + + /** + * Gets the size of this instance, in 16-bit code units. This will only + * return a meaningful result if the instructions in this instance all + * have valid addresses. + * + * @return {@code >= 0;} the size + */ + public int codeSize() { + int sz = size(); + + if (sz == 0) { + return 0; + } + + DalvInsn last = get(sz - 1); + return last.getNextAddress(); + } + + /** + * Writes all the instructions in this instance to the given output + * destination. + * + * @param out {@code non-null;} where to write to + */ + public void writeTo(AnnotatedOutput out) { + int startCursor = out.getCursor(); + int sz = size(); + + if (out.annotates()) { + boolean verbose = out.isVerbose(); + + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + int codeBytes = insn.codeSize() * 2; + String s; + + if ((codeBytes != 0) || verbose) { + s = insn.listingString(" ", out.getAnnotationWidth(), + true); + } else { + s = null; + } + + if (s != null) { + out.annotate(codeBytes, s); + } else if (codeBytes != 0) { + out.annotate(codeBytes, ""); + } + } + } + + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + try { + insn.writeTo(out); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing " + insn); + } + } + + // Sanity check of the amount written. + int written = (out.getCursor() - startCursor) / 2; + if (written != codeSize()) { + throw new RuntimeException("write length mismatch; expected " + + codeSize() + " but actually wrote " + written); + } + } + + /** + * Gets the minimum required register count implied by this + * instance. This includes any unused parameters that could + * potentially be at the top of the register space. + * @return {@code >= 0;} the required registers size + */ + public int getRegistersSize() { + return regCount; + } + + /** + * Gets the size of the outgoing arguments area required by this + * method. This is equal to the largest argument word count of any + * method referred to by this instance. + * + * @return {@code >= 0;} the required outgoing arguments size + */ + public int getOutsSize() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + int count = 0; + + if (insn instanceof CstInsn) { + Constant cst = ((CstInsn) insn).getConstant(); + if (cst instanceof CstBaseMethodRef) { + CstBaseMethodRef methodRef = (CstBaseMethodRef) cst; + boolean isStatic = + (insn.getOpcode().getFamily() == Opcodes.INVOKE_STATIC); + count = methodRef.getParameterWordCount(isStatic); + } + } else if (insn instanceof MultiCstInsn) { + if (insn.getOpcode().getFamily() != Opcodes.INVOKE_POLYMORPHIC) { + throw new RuntimeException("Expecting invoke-polymorphic"); + } + MultiCstInsn mci = (MultiCstInsn) insn; + // Invoke-polymorphic has two constants: [0] method-ref and + // [1] call site prototype. The number of arguments is based + // on the call site prototype since these are the arguments + // presented. The method-ref is always MethodHandle.invoke(Object[]) + // or MethodHandle.invokeExact(Object[]). + CstProtoRef proto = (CstProtoRef) mci.getConstant(1); + count = proto.getPrototype().getParameterTypes().getWordCount(); + count = count + 1; // And one for receiver (method handle). + } else { + continue; + } + + if (count > result) { + result = count; + } + } + + return result; + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + * @param verbose whether to be verbose; verbose output includes + * lines for zero-size instructions and explicit constant pool indices + */ + public void debugPrint(Writer out, String prefix, boolean verbose) { + IndentingWriter iw = new IndentingWriter(out, 0, prefix); + int sz = size(); + + try { + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + String s; + + if ((insn.codeSize() != 0) || verbose) { + s = insn.listingString("", 0, verbose); + } else { + s = null; + } + + if (s != null) { + iw.write(s); + } + } + + iw.flush(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + * @param verbose whether to be verbose; verbose output includes + * lines for zero-size instructions + */ + public void debugPrint(OutputStream out, String prefix, boolean verbose) { + Writer w = new OutputStreamWriter(out); + debugPrint(w, prefix, verbose); + + try { + w.flush(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/Dop.java b/dexlib/src/main/java/com/android/dx/dex/code/Dop.java new file mode 100644 index 000000000..51d1b5160 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/Dop.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.io.OpcodeInfo; +import com.android.dx.io.Opcodes; + +/** + * Representation of an opcode. + */ +public final class Dop { + /** {@code Opcodes.isValid();} the opcode value itself */ + private final int opcode; + + /** {@code Opcodes.isValid();} the opcode family */ + private final int family; + + /** + * {@code Opcodes.isValid();} what opcode (by number) to try next + * when attempting to match an opcode to particular arguments; + * {@code Opcodes.NO_NEXT} to indicate that this is the last + * opcode to try in a particular chain + */ + private final int nextOpcode; + + /** {@code non-null;} the instruction format */ + private final InsnFormat format; + + /** whether this opcode uses a result register */ + private final boolean hasResult; + + /** + * Constructs an instance. + * + * @param opcode {@code Opcodes.isValid();} the opcode value + * itself + * @param family {@code Opcodes.isValid();} the opcode family + * @param nextOpcode {@code Opcodes.isValid();} what opcode (by + * number) to try next when attempting to match an opcode to + * particular arguments; {@code Opcodes.NO_NEXT} to indicate that + * this is the last opcode to try in a particular chain + * @param format {@code non-null;} the instruction format + * @param hasResult whether the opcode has a result register; if so it + * is always the first register + */ + public Dop(int opcode, int family, int nextOpcode, InsnFormat format, + boolean hasResult) { + if (!Opcodes.isValidShape(opcode)) { + throw new IllegalArgumentException("bogus opcode"); + } + + if (!Opcodes.isValidShape(family)) { + throw new IllegalArgumentException("bogus family"); + } + + if (!Opcodes.isValidShape(nextOpcode)) { + throw new IllegalArgumentException("bogus nextOpcode"); + } + + if (format == null) { + throw new NullPointerException("format == null"); + } + + this.opcode = opcode; + this.family = family; + this.nextOpcode = nextOpcode; + this.format = format; + this.hasResult = hasResult; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return getName(); + } + + /** + * Gets the opcode value. + * + * @return {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the opcode value + */ + public int getOpcode() { + return opcode; + } + + /** + * Gets the opcode family. The opcode family is the unmarked (no + * "/...") opcode that has equivalent semantics to this one. + * + * @return {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the opcode family + */ + public int getFamily() { + return family; + } + + /** + * Gets the instruction format. + * + * @return {@code non-null;} the instruction format + */ + public InsnFormat getFormat() { + return format; + } + + /** + * Returns whether this opcode uses a result register. + * + * @return {@code true} iff this opcode uses a result register + */ + public boolean hasResult() { + return hasResult; + } + + /** + * Gets the opcode name. + * + * @return {@code non-null;} the opcode name + */ + public String getName() { + return OpcodeInfo.getName(opcode); + } + + /** + * Gets the opcode value to try next when attempting to match an + * opcode to particular arguments. This returns {@code + * Opcodes.NO_NEXT} to indicate that this is the last opcode to + * try in a particular chain. + * + * @return {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the opcode value + */ + public int getNextOpcode() { + return nextOpcode; + } + + /** + * Gets the opcode for the opposite test of this instance. This is only + * valid for opcodes which are in fact tests. + * + * @return {@code non-null;} the opposite test + */ + public Dop getOppositeTest() { + switch (opcode) { + case Opcodes.IF_EQ: return Dops.IF_NE; + case Opcodes.IF_NE: return Dops.IF_EQ; + case Opcodes.IF_LT: return Dops.IF_GE; + case Opcodes.IF_GE: return Dops.IF_LT; + case Opcodes.IF_GT: return Dops.IF_LE; + case Opcodes.IF_LE: return Dops.IF_GT; + case Opcodes.IF_EQZ: return Dops.IF_NEZ; + case Opcodes.IF_NEZ: return Dops.IF_EQZ; + case Opcodes.IF_LTZ: return Dops.IF_GEZ; + case Opcodes.IF_GEZ: return Dops.IF_LTZ; + case Opcodes.IF_GTZ: return Dops.IF_LEZ; + case Opcodes.IF_LEZ: return Dops.IF_GTZ; + } + + throw new IllegalArgumentException("bogus opcode: " + this); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/Dops.java b/dexlib/src/main/java/com/android/dx/dex/code/Dops.java new file mode 100644 index 000000000..cc8769563 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/Dops.java @@ -0,0 +1,1251 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.dex.DexOptions; +import com.android.dx.dex.code.form.Form10t; +import com.android.dx.dex.code.form.Form10x; +import com.android.dx.dex.code.form.Form11n; +import com.android.dx.dex.code.form.Form11x; +import com.android.dx.dex.code.form.Form12x; +import com.android.dx.dex.code.form.Form20t; +import com.android.dx.dex.code.form.Form21c; +import com.android.dx.dex.code.form.Form21h; +import com.android.dx.dex.code.form.Form21s; +import com.android.dx.dex.code.form.Form21t; +import com.android.dx.dex.code.form.Form22b; +import com.android.dx.dex.code.form.Form22c; +import com.android.dx.dex.code.form.Form22s; +import com.android.dx.dex.code.form.Form22t; +import com.android.dx.dex.code.form.Form22x; +import com.android.dx.dex.code.form.Form23x; +import com.android.dx.dex.code.form.Form30t; +import com.android.dx.dex.code.form.Form31c; +import com.android.dx.dex.code.form.Form31i; +import com.android.dx.dex.code.form.Form31t; +import com.android.dx.dex.code.form.Form32x; +import com.android.dx.dex.code.form.Form35c; +import com.android.dx.dex.code.form.Form3rc; +import com.android.dx.dex.code.form.Form45cc; +import com.android.dx.dex.code.form.Form4rcc; +import com.android.dx.dex.code.form.Form51l; +import com.android.dx.dex.code.form.SpecialFormat; +import com.android.dx.io.Opcodes; + +/** + * Standard instances of {@link Dop} and utility methods for getting + * them. + */ +public final class Dops { + /** {@code non-null;} array containing all the standard instances */ + private static final Dop[] DOPS; + + /** + * pseudo-opcode used for nonstandard formatted "instructions" + * (which are mostly not actually instructions, though they do + * appear in instruction lists). TODO: Retire the usage of this + * constant. + */ + public static final Dop SPECIAL_FORMAT = + new Dop(Opcodes.SPECIAL_FORMAT, Opcodes.SPECIAL_FORMAT, + Opcodes.NO_NEXT, SpecialFormat.THE_ONE, false); + + // BEGIN(dops); GENERATED AUTOMATICALLY BY opcode-gen + public static final Dop NOP = + new Dop(Opcodes.NOP, Opcodes.NOP, + Opcodes.NO_NEXT, Form10x.THE_ONE, false); + + public static final Dop MOVE = + new Dop(Opcodes.MOVE, Opcodes.MOVE, + Opcodes.MOVE_FROM16, Form12x.THE_ONE, true); + + public static final Dop MOVE_FROM16 = + new Dop(Opcodes.MOVE_FROM16, Opcodes.MOVE, + Opcodes.MOVE_16, Form22x.THE_ONE, true); + + public static final Dop MOVE_16 = + new Dop(Opcodes.MOVE_16, Opcodes.MOVE, + Opcodes.NO_NEXT, Form32x.THE_ONE, true); + + public static final Dop MOVE_WIDE = + new Dop(Opcodes.MOVE_WIDE, Opcodes.MOVE_WIDE, + Opcodes.MOVE_WIDE_FROM16, Form12x.THE_ONE, true); + + public static final Dop MOVE_WIDE_FROM16 = + new Dop(Opcodes.MOVE_WIDE_FROM16, Opcodes.MOVE_WIDE, + Opcodes.MOVE_WIDE_16, Form22x.THE_ONE, true); + + public static final Dop MOVE_WIDE_16 = + new Dop(Opcodes.MOVE_WIDE_16, Opcodes.MOVE_WIDE, + Opcodes.NO_NEXT, Form32x.THE_ONE, true); + + public static final Dop MOVE_OBJECT = + new Dop(Opcodes.MOVE_OBJECT, Opcodes.MOVE_OBJECT, + Opcodes.MOVE_OBJECT_FROM16, Form12x.THE_ONE, true); + + public static final Dop MOVE_OBJECT_FROM16 = + new Dop(Opcodes.MOVE_OBJECT_FROM16, Opcodes.MOVE_OBJECT, + Opcodes.MOVE_OBJECT_16, Form22x.THE_ONE, true); + + public static final Dop MOVE_OBJECT_16 = + new Dop(Opcodes.MOVE_OBJECT_16, Opcodes.MOVE_OBJECT, + Opcodes.NO_NEXT, Form32x.THE_ONE, true); + + public static final Dop MOVE_RESULT = + new Dop(Opcodes.MOVE_RESULT, Opcodes.MOVE_RESULT, + Opcodes.NO_NEXT, Form11x.THE_ONE, true); + + public static final Dop MOVE_RESULT_WIDE = + new Dop(Opcodes.MOVE_RESULT_WIDE, Opcodes.MOVE_RESULT_WIDE, + Opcodes.NO_NEXT, Form11x.THE_ONE, true); + + public static final Dop MOVE_RESULT_OBJECT = + new Dop(Opcodes.MOVE_RESULT_OBJECT, Opcodes.MOVE_RESULT_OBJECT, + Opcodes.NO_NEXT, Form11x.THE_ONE, true); + + public static final Dop MOVE_EXCEPTION = + new Dop(Opcodes.MOVE_EXCEPTION, Opcodes.MOVE_EXCEPTION, + Opcodes.NO_NEXT, Form11x.THE_ONE, true); + + public static final Dop RETURN_VOID = + new Dop(Opcodes.RETURN_VOID, Opcodes.RETURN_VOID, + Opcodes.NO_NEXT, Form10x.THE_ONE, false); + + public static final Dop RETURN = + new Dop(Opcodes.RETURN, Opcodes.RETURN, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop RETURN_WIDE = + new Dop(Opcodes.RETURN_WIDE, Opcodes.RETURN_WIDE, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop RETURN_OBJECT = + new Dop(Opcodes.RETURN_OBJECT, Opcodes.RETURN_OBJECT, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop CONST_4 = + new Dop(Opcodes.CONST_4, Opcodes.CONST, + Opcodes.CONST_16, Form11n.THE_ONE, true); + + public static final Dop CONST_16 = + new Dop(Opcodes.CONST_16, Opcodes.CONST, + Opcodes.CONST_HIGH16, Form21s.THE_ONE, true); + + public static final Dop CONST = + new Dop(Opcodes.CONST, Opcodes.CONST, + Opcodes.NO_NEXT, Form31i.THE_ONE, true); + + public static final Dop CONST_HIGH16 = + new Dop(Opcodes.CONST_HIGH16, Opcodes.CONST, + Opcodes.CONST, Form21h.THE_ONE, true); + + public static final Dop CONST_WIDE_16 = + new Dop(Opcodes.CONST_WIDE_16, Opcodes.CONST_WIDE, + Opcodes.CONST_WIDE_HIGH16, Form21s.THE_ONE, true); + + public static final Dop CONST_WIDE_32 = + new Dop(Opcodes.CONST_WIDE_32, Opcodes.CONST_WIDE, + Opcodes.CONST_WIDE, Form31i.THE_ONE, true); + + public static final Dop CONST_WIDE = + new Dop(Opcodes.CONST_WIDE, Opcodes.CONST_WIDE, + Opcodes.NO_NEXT, Form51l.THE_ONE, true); + + public static final Dop CONST_WIDE_HIGH16 = + new Dop(Opcodes.CONST_WIDE_HIGH16, Opcodes.CONST_WIDE, + Opcodes.CONST_WIDE_32, Form21h.THE_ONE, true); + + public static final Dop CONST_STRING = + new Dop(Opcodes.CONST_STRING, Opcodes.CONST_STRING, + Opcodes.CONST_STRING_JUMBO, Form21c.THE_ONE, true); + + public static final Dop CONST_STRING_JUMBO = + new Dop(Opcodes.CONST_STRING_JUMBO, Opcodes.CONST_STRING, + Opcodes.NO_NEXT, Form31c.THE_ONE, true); + + public static final Dop CONST_CLASS = + new Dop(Opcodes.CONST_CLASS, Opcodes.CONST_CLASS, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop MONITOR_ENTER = + new Dop(Opcodes.MONITOR_ENTER, Opcodes.MONITOR_ENTER, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop MONITOR_EXIT = + new Dop(Opcodes.MONITOR_EXIT, Opcodes.MONITOR_EXIT, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop CHECK_CAST = + new Dop(Opcodes.CHECK_CAST, Opcodes.CHECK_CAST, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop INSTANCE_OF = + new Dop(Opcodes.INSTANCE_OF, Opcodes.INSTANCE_OF, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop ARRAY_LENGTH = + new Dop(Opcodes.ARRAY_LENGTH, Opcodes.ARRAY_LENGTH, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NEW_INSTANCE = + new Dop(Opcodes.NEW_INSTANCE, Opcodes.NEW_INSTANCE, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop NEW_ARRAY = + new Dop(Opcodes.NEW_ARRAY, Opcodes.NEW_ARRAY, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop FILLED_NEW_ARRAY = + new Dop(Opcodes.FILLED_NEW_ARRAY, Opcodes.FILLED_NEW_ARRAY, + Opcodes.FILLED_NEW_ARRAY_RANGE, Form35c.THE_ONE, false); + + public static final Dop FILLED_NEW_ARRAY_RANGE = + new Dop(Opcodes.FILLED_NEW_ARRAY_RANGE, Opcodes.FILLED_NEW_ARRAY, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop FILL_ARRAY_DATA = + new Dop(Opcodes.FILL_ARRAY_DATA, Opcodes.FILL_ARRAY_DATA, + Opcodes.NO_NEXT, Form31t.THE_ONE, false); + + public static final Dop THROW = + new Dop(Opcodes.THROW, Opcodes.THROW, + Opcodes.NO_NEXT, Form11x.THE_ONE, false); + + public static final Dop GOTO = + new Dop(Opcodes.GOTO, Opcodes.GOTO, + Opcodes.GOTO_16, Form10t.THE_ONE, false); + + public static final Dop GOTO_16 = + new Dop(Opcodes.GOTO_16, Opcodes.GOTO, + Opcodes.GOTO_32, Form20t.THE_ONE, false); + + public static final Dop GOTO_32 = + new Dop(Opcodes.GOTO_32, Opcodes.GOTO, + Opcodes.NO_NEXT, Form30t.THE_ONE, false); + + public static final Dop PACKED_SWITCH = + new Dop(Opcodes.PACKED_SWITCH, Opcodes.PACKED_SWITCH, + Opcodes.NO_NEXT, Form31t.THE_ONE, false); + + public static final Dop SPARSE_SWITCH = + new Dop(Opcodes.SPARSE_SWITCH, Opcodes.SPARSE_SWITCH, + Opcodes.NO_NEXT, Form31t.THE_ONE, false); + + public static final Dop CMPL_FLOAT = + new Dop(Opcodes.CMPL_FLOAT, Opcodes.CMPL_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop CMPG_FLOAT = + new Dop(Opcodes.CMPG_FLOAT, Opcodes.CMPG_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop CMPL_DOUBLE = + new Dop(Opcodes.CMPL_DOUBLE, Opcodes.CMPL_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop CMPG_DOUBLE = + new Dop(Opcodes.CMPG_DOUBLE, Opcodes.CMPG_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop CMP_LONG = + new Dop(Opcodes.CMP_LONG, Opcodes.CMP_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop IF_EQ = + new Dop(Opcodes.IF_EQ, Opcodes.IF_EQ, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_NE = + new Dop(Opcodes.IF_NE, Opcodes.IF_NE, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_LT = + new Dop(Opcodes.IF_LT, Opcodes.IF_LT, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_GE = + new Dop(Opcodes.IF_GE, Opcodes.IF_GE, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_GT = + new Dop(Opcodes.IF_GT, Opcodes.IF_GT, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_LE = + new Dop(Opcodes.IF_LE, Opcodes.IF_LE, + Opcodes.NO_NEXT, Form22t.THE_ONE, false); + + public static final Dop IF_EQZ = + new Dop(Opcodes.IF_EQZ, Opcodes.IF_EQZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_NEZ = + new Dop(Opcodes.IF_NEZ, Opcodes.IF_NEZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_LTZ = + new Dop(Opcodes.IF_LTZ, Opcodes.IF_LTZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_GEZ = + new Dop(Opcodes.IF_GEZ, Opcodes.IF_GEZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_GTZ = + new Dop(Opcodes.IF_GTZ, Opcodes.IF_GTZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop IF_LEZ = + new Dop(Opcodes.IF_LEZ, Opcodes.IF_LEZ, + Opcodes.NO_NEXT, Form21t.THE_ONE, false); + + public static final Dop AGET = + new Dop(Opcodes.AGET, Opcodes.AGET, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_WIDE = + new Dop(Opcodes.AGET_WIDE, Opcodes.AGET_WIDE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_OBJECT = + new Dop(Opcodes.AGET_OBJECT, Opcodes.AGET_OBJECT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_BOOLEAN = + new Dop(Opcodes.AGET_BOOLEAN, Opcodes.AGET_BOOLEAN, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_BYTE = + new Dop(Opcodes.AGET_BYTE, Opcodes.AGET_BYTE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_CHAR = + new Dop(Opcodes.AGET_CHAR, Opcodes.AGET_CHAR, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AGET_SHORT = + new Dop(Opcodes.AGET_SHORT, Opcodes.AGET_SHORT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop APUT = + new Dop(Opcodes.APUT, Opcodes.APUT, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_WIDE = + new Dop(Opcodes.APUT_WIDE, Opcodes.APUT_WIDE, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_OBJECT = + new Dop(Opcodes.APUT_OBJECT, Opcodes.APUT_OBJECT, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_BOOLEAN = + new Dop(Opcodes.APUT_BOOLEAN, Opcodes.APUT_BOOLEAN, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_BYTE = + new Dop(Opcodes.APUT_BYTE, Opcodes.APUT_BYTE, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_CHAR = + new Dop(Opcodes.APUT_CHAR, Opcodes.APUT_CHAR, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop APUT_SHORT = + new Dop(Opcodes.APUT_SHORT, Opcodes.APUT_SHORT, + Opcodes.NO_NEXT, Form23x.THE_ONE, false); + + public static final Dop IGET = + new Dop(Opcodes.IGET, Opcodes.IGET, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_WIDE = + new Dop(Opcodes.IGET_WIDE, Opcodes.IGET_WIDE, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_OBJECT = + new Dop(Opcodes.IGET_OBJECT, Opcodes.IGET_OBJECT, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_BOOLEAN = + new Dop(Opcodes.IGET_BOOLEAN, Opcodes.IGET_BOOLEAN, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_BYTE = + new Dop(Opcodes.IGET_BYTE, Opcodes.IGET_BYTE, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_CHAR = + new Dop(Opcodes.IGET_CHAR, Opcodes.IGET_CHAR, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IGET_SHORT = + new Dop(Opcodes.IGET_SHORT, Opcodes.IGET_SHORT, + Opcodes.NO_NEXT, Form22c.THE_ONE, true); + + public static final Dop IPUT = + new Dop(Opcodes.IPUT, Opcodes.IPUT, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_WIDE = + new Dop(Opcodes.IPUT_WIDE, Opcodes.IPUT_WIDE, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_OBJECT = + new Dop(Opcodes.IPUT_OBJECT, Opcodes.IPUT_OBJECT, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_BOOLEAN = + new Dop(Opcodes.IPUT_BOOLEAN, Opcodes.IPUT_BOOLEAN, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_BYTE = + new Dop(Opcodes.IPUT_BYTE, Opcodes.IPUT_BYTE, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_CHAR = + new Dop(Opcodes.IPUT_CHAR, Opcodes.IPUT_CHAR, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop IPUT_SHORT = + new Dop(Opcodes.IPUT_SHORT, Opcodes.IPUT_SHORT, + Opcodes.NO_NEXT, Form22c.THE_ONE, false); + + public static final Dop SGET = + new Dop(Opcodes.SGET, Opcodes.SGET, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_WIDE = + new Dop(Opcodes.SGET_WIDE, Opcodes.SGET_WIDE, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_OBJECT = + new Dop(Opcodes.SGET_OBJECT, Opcodes.SGET_OBJECT, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_BOOLEAN = + new Dop(Opcodes.SGET_BOOLEAN, Opcodes.SGET_BOOLEAN, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_BYTE = + new Dop(Opcodes.SGET_BYTE, Opcodes.SGET_BYTE, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_CHAR = + new Dop(Opcodes.SGET_CHAR, Opcodes.SGET_CHAR, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SGET_SHORT = + new Dop(Opcodes.SGET_SHORT, Opcodes.SGET_SHORT, + Opcodes.NO_NEXT, Form21c.THE_ONE, true); + + public static final Dop SPUT = + new Dop(Opcodes.SPUT, Opcodes.SPUT, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_WIDE = + new Dop(Opcodes.SPUT_WIDE, Opcodes.SPUT_WIDE, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_OBJECT = + new Dop(Opcodes.SPUT_OBJECT, Opcodes.SPUT_OBJECT, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_BOOLEAN = + new Dop(Opcodes.SPUT_BOOLEAN, Opcodes.SPUT_BOOLEAN, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_BYTE = + new Dop(Opcodes.SPUT_BYTE, Opcodes.SPUT_BYTE, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_CHAR = + new Dop(Opcodes.SPUT_CHAR, Opcodes.SPUT_CHAR, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop SPUT_SHORT = + new Dop(Opcodes.SPUT_SHORT, Opcodes.SPUT_SHORT, + Opcodes.NO_NEXT, Form21c.THE_ONE, false); + + public static final Dop INVOKE_VIRTUAL = + new Dop(Opcodes.INVOKE_VIRTUAL, Opcodes.INVOKE_VIRTUAL, + Opcodes.INVOKE_VIRTUAL_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_SUPER = + new Dop(Opcodes.INVOKE_SUPER, Opcodes.INVOKE_SUPER, + Opcodes.INVOKE_SUPER_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_DIRECT = + new Dop(Opcodes.INVOKE_DIRECT, Opcodes.INVOKE_DIRECT, + Opcodes.INVOKE_DIRECT_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_STATIC = + new Dop(Opcodes.INVOKE_STATIC, Opcodes.INVOKE_STATIC, + Opcodes.INVOKE_STATIC_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_INTERFACE = + new Dop(Opcodes.INVOKE_INTERFACE, Opcodes.INVOKE_INTERFACE, + Opcodes.INVOKE_INTERFACE_RANGE, Form35c.THE_ONE, false); + + public static final Dop INVOKE_VIRTUAL_RANGE = + new Dop(Opcodes.INVOKE_VIRTUAL_RANGE, Opcodes.INVOKE_VIRTUAL, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop INVOKE_SUPER_RANGE = + new Dop(Opcodes.INVOKE_SUPER_RANGE, Opcodes.INVOKE_SUPER, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop INVOKE_DIRECT_RANGE = + new Dop(Opcodes.INVOKE_DIRECT_RANGE, Opcodes.INVOKE_DIRECT, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop INVOKE_STATIC_RANGE = + new Dop(Opcodes.INVOKE_STATIC_RANGE, Opcodes.INVOKE_STATIC, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop INVOKE_INTERFACE_RANGE = + new Dop(Opcodes.INVOKE_INTERFACE_RANGE, Opcodes.INVOKE_INTERFACE, + Opcodes.NO_NEXT, Form3rc.THE_ONE, false); + + public static final Dop NEG_INT = + new Dop(Opcodes.NEG_INT, Opcodes.NEG_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NOT_INT = + new Dop(Opcodes.NOT_INT, Opcodes.NOT_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NEG_LONG = + new Dop(Opcodes.NEG_LONG, Opcodes.NEG_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NOT_LONG = + new Dop(Opcodes.NOT_LONG, Opcodes.NOT_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NEG_FLOAT = + new Dop(Opcodes.NEG_FLOAT, Opcodes.NEG_FLOAT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop NEG_DOUBLE = + new Dop(Opcodes.NEG_DOUBLE, Opcodes.NEG_DOUBLE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_LONG = + new Dop(Opcodes.INT_TO_LONG, Opcodes.INT_TO_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_FLOAT = + new Dop(Opcodes.INT_TO_FLOAT, Opcodes.INT_TO_FLOAT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_DOUBLE = + new Dop(Opcodes.INT_TO_DOUBLE, Opcodes.INT_TO_DOUBLE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop LONG_TO_INT = + new Dop(Opcodes.LONG_TO_INT, Opcodes.LONG_TO_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop LONG_TO_FLOAT = + new Dop(Opcodes.LONG_TO_FLOAT, Opcodes.LONG_TO_FLOAT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop LONG_TO_DOUBLE = + new Dop(Opcodes.LONG_TO_DOUBLE, Opcodes.LONG_TO_DOUBLE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop FLOAT_TO_INT = + new Dop(Opcodes.FLOAT_TO_INT, Opcodes.FLOAT_TO_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop FLOAT_TO_LONG = + new Dop(Opcodes.FLOAT_TO_LONG, Opcodes.FLOAT_TO_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop FLOAT_TO_DOUBLE = + new Dop(Opcodes.FLOAT_TO_DOUBLE, Opcodes.FLOAT_TO_DOUBLE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop DOUBLE_TO_INT = + new Dop(Opcodes.DOUBLE_TO_INT, Opcodes.DOUBLE_TO_INT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop DOUBLE_TO_LONG = + new Dop(Opcodes.DOUBLE_TO_LONG, Opcodes.DOUBLE_TO_LONG, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop DOUBLE_TO_FLOAT = + new Dop(Opcodes.DOUBLE_TO_FLOAT, Opcodes.DOUBLE_TO_FLOAT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_BYTE = + new Dop(Opcodes.INT_TO_BYTE, Opcodes.INT_TO_BYTE, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_CHAR = + new Dop(Opcodes.INT_TO_CHAR, Opcodes.INT_TO_CHAR, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop INT_TO_SHORT = + new Dop(Opcodes.INT_TO_SHORT, Opcodes.INT_TO_SHORT, + Opcodes.NO_NEXT, Form12x.THE_ONE, true); + + public static final Dop ADD_INT = + new Dop(Opcodes.ADD_INT, Opcodes.ADD_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SUB_INT = + new Dop(Opcodes.SUB_INT, Opcodes.SUB_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop MUL_INT = + new Dop(Opcodes.MUL_INT, Opcodes.MUL_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop DIV_INT = + new Dop(Opcodes.DIV_INT, Opcodes.DIV_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop REM_INT = + new Dop(Opcodes.REM_INT, Opcodes.REM_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AND_INT = + new Dop(Opcodes.AND_INT, Opcodes.AND_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop OR_INT = + new Dop(Opcodes.OR_INT, Opcodes.OR_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop XOR_INT = + new Dop(Opcodes.XOR_INT, Opcodes.XOR_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SHL_INT = + new Dop(Opcodes.SHL_INT, Opcodes.SHL_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SHR_INT = + new Dop(Opcodes.SHR_INT, Opcodes.SHR_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop USHR_INT = + new Dop(Opcodes.USHR_INT, Opcodes.USHR_INT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop ADD_LONG = + new Dop(Opcodes.ADD_LONG, Opcodes.ADD_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SUB_LONG = + new Dop(Opcodes.SUB_LONG, Opcodes.SUB_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop MUL_LONG = + new Dop(Opcodes.MUL_LONG, Opcodes.MUL_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop DIV_LONG = + new Dop(Opcodes.DIV_LONG, Opcodes.DIV_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop REM_LONG = + new Dop(Opcodes.REM_LONG, Opcodes.REM_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop AND_LONG = + new Dop(Opcodes.AND_LONG, Opcodes.AND_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop OR_LONG = + new Dop(Opcodes.OR_LONG, Opcodes.OR_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop XOR_LONG = + new Dop(Opcodes.XOR_LONG, Opcodes.XOR_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SHL_LONG = + new Dop(Opcodes.SHL_LONG, Opcodes.SHL_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SHR_LONG = + new Dop(Opcodes.SHR_LONG, Opcodes.SHR_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop USHR_LONG = + new Dop(Opcodes.USHR_LONG, Opcodes.USHR_LONG, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop ADD_FLOAT = + new Dop(Opcodes.ADD_FLOAT, Opcodes.ADD_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SUB_FLOAT = + new Dop(Opcodes.SUB_FLOAT, Opcodes.SUB_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop MUL_FLOAT = + new Dop(Opcodes.MUL_FLOAT, Opcodes.MUL_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop DIV_FLOAT = + new Dop(Opcodes.DIV_FLOAT, Opcodes.DIV_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop REM_FLOAT = + new Dop(Opcodes.REM_FLOAT, Opcodes.REM_FLOAT, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop ADD_DOUBLE = + new Dop(Opcodes.ADD_DOUBLE, Opcodes.ADD_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop SUB_DOUBLE = + new Dop(Opcodes.SUB_DOUBLE, Opcodes.SUB_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop MUL_DOUBLE = + new Dop(Opcodes.MUL_DOUBLE, Opcodes.MUL_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop DIV_DOUBLE = + new Dop(Opcodes.DIV_DOUBLE, Opcodes.DIV_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop REM_DOUBLE = + new Dop(Opcodes.REM_DOUBLE, Opcodes.REM_DOUBLE, + Opcodes.NO_NEXT, Form23x.THE_ONE, true); + + public static final Dop ADD_INT_2ADDR = + new Dop(Opcodes.ADD_INT_2ADDR, Opcodes.ADD_INT, + Opcodes.ADD_INT, Form12x.THE_ONE, true); + + public static final Dop SUB_INT_2ADDR = + new Dop(Opcodes.SUB_INT_2ADDR, Opcodes.SUB_INT, + Opcodes.SUB_INT, Form12x.THE_ONE, true); + + public static final Dop MUL_INT_2ADDR = + new Dop(Opcodes.MUL_INT_2ADDR, Opcodes.MUL_INT, + Opcodes.MUL_INT, Form12x.THE_ONE, true); + + public static final Dop DIV_INT_2ADDR = + new Dop(Opcodes.DIV_INT_2ADDR, Opcodes.DIV_INT, + Opcodes.DIV_INT, Form12x.THE_ONE, true); + + public static final Dop REM_INT_2ADDR = + new Dop(Opcodes.REM_INT_2ADDR, Opcodes.REM_INT, + Opcodes.REM_INT, Form12x.THE_ONE, true); + + public static final Dop AND_INT_2ADDR = + new Dop(Opcodes.AND_INT_2ADDR, Opcodes.AND_INT, + Opcodes.AND_INT, Form12x.THE_ONE, true); + + public static final Dop OR_INT_2ADDR = + new Dop(Opcodes.OR_INT_2ADDR, Opcodes.OR_INT, + Opcodes.OR_INT, Form12x.THE_ONE, true); + + public static final Dop XOR_INT_2ADDR = + new Dop(Opcodes.XOR_INT_2ADDR, Opcodes.XOR_INT, + Opcodes.XOR_INT, Form12x.THE_ONE, true); + + public static final Dop SHL_INT_2ADDR = + new Dop(Opcodes.SHL_INT_2ADDR, Opcodes.SHL_INT, + Opcodes.SHL_INT, Form12x.THE_ONE, true); + + public static final Dop SHR_INT_2ADDR = + new Dop(Opcodes.SHR_INT_2ADDR, Opcodes.SHR_INT, + Opcodes.SHR_INT, Form12x.THE_ONE, true); + + public static final Dop USHR_INT_2ADDR = + new Dop(Opcodes.USHR_INT_2ADDR, Opcodes.USHR_INT, + Opcodes.USHR_INT, Form12x.THE_ONE, true); + + public static final Dop ADD_LONG_2ADDR = + new Dop(Opcodes.ADD_LONG_2ADDR, Opcodes.ADD_LONG, + Opcodes.ADD_LONG, Form12x.THE_ONE, true); + + public static final Dop SUB_LONG_2ADDR = + new Dop(Opcodes.SUB_LONG_2ADDR, Opcodes.SUB_LONG, + Opcodes.SUB_LONG, Form12x.THE_ONE, true); + + public static final Dop MUL_LONG_2ADDR = + new Dop(Opcodes.MUL_LONG_2ADDR, Opcodes.MUL_LONG, + Opcodes.MUL_LONG, Form12x.THE_ONE, true); + + public static final Dop DIV_LONG_2ADDR = + new Dop(Opcodes.DIV_LONG_2ADDR, Opcodes.DIV_LONG, + Opcodes.DIV_LONG, Form12x.THE_ONE, true); + + public static final Dop REM_LONG_2ADDR = + new Dop(Opcodes.REM_LONG_2ADDR, Opcodes.REM_LONG, + Opcodes.REM_LONG, Form12x.THE_ONE, true); + + public static final Dop AND_LONG_2ADDR = + new Dop(Opcodes.AND_LONG_2ADDR, Opcodes.AND_LONG, + Opcodes.AND_LONG, Form12x.THE_ONE, true); + + public static final Dop OR_LONG_2ADDR = + new Dop(Opcodes.OR_LONG_2ADDR, Opcodes.OR_LONG, + Opcodes.OR_LONG, Form12x.THE_ONE, true); + + public static final Dop XOR_LONG_2ADDR = + new Dop(Opcodes.XOR_LONG_2ADDR, Opcodes.XOR_LONG, + Opcodes.XOR_LONG, Form12x.THE_ONE, true); + + public static final Dop SHL_LONG_2ADDR = + new Dop(Opcodes.SHL_LONG_2ADDR, Opcodes.SHL_LONG, + Opcodes.SHL_LONG, Form12x.THE_ONE, true); + + public static final Dop SHR_LONG_2ADDR = + new Dop(Opcodes.SHR_LONG_2ADDR, Opcodes.SHR_LONG, + Opcodes.SHR_LONG, Form12x.THE_ONE, true); + + public static final Dop USHR_LONG_2ADDR = + new Dop(Opcodes.USHR_LONG_2ADDR, Opcodes.USHR_LONG, + Opcodes.USHR_LONG, Form12x.THE_ONE, true); + + public static final Dop ADD_FLOAT_2ADDR = + new Dop(Opcodes.ADD_FLOAT_2ADDR, Opcodes.ADD_FLOAT, + Opcodes.ADD_FLOAT, Form12x.THE_ONE, true); + + public static final Dop SUB_FLOAT_2ADDR = + new Dop(Opcodes.SUB_FLOAT_2ADDR, Opcodes.SUB_FLOAT, + Opcodes.SUB_FLOAT, Form12x.THE_ONE, true); + + public static final Dop MUL_FLOAT_2ADDR = + new Dop(Opcodes.MUL_FLOAT_2ADDR, Opcodes.MUL_FLOAT, + Opcodes.MUL_FLOAT, Form12x.THE_ONE, true); + + public static final Dop DIV_FLOAT_2ADDR = + new Dop(Opcodes.DIV_FLOAT_2ADDR, Opcodes.DIV_FLOAT, + Opcodes.DIV_FLOAT, Form12x.THE_ONE, true); + + public static final Dop REM_FLOAT_2ADDR = + new Dop(Opcodes.REM_FLOAT_2ADDR, Opcodes.REM_FLOAT, + Opcodes.REM_FLOAT, Form12x.THE_ONE, true); + + public static final Dop ADD_DOUBLE_2ADDR = + new Dop(Opcodes.ADD_DOUBLE_2ADDR, Opcodes.ADD_DOUBLE, + Opcodes.ADD_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop SUB_DOUBLE_2ADDR = + new Dop(Opcodes.SUB_DOUBLE_2ADDR, Opcodes.SUB_DOUBLE, + Opcodes.SUB_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop MUL_DOUBLE_2ADDR = + new Dop(Opcodes.MUL_DOUBLE_2ADDR, Opcodes.MUL_DOUBLE, + Opcodes.MUL_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop DIV_DOUBLE_2ADDR = + new Dop(Opcodes.DIV_DOUBLE_2ADDR, Opcodes.DIV_DOUBLE, + Opcodes.DIV_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop REM_DOUBLE_2ADDR = + new Dop(Opcodes.REM_DOUBLE_2ADDR, Opcodes.REM_DOUBLE, + Opcodes.REM_DOUBLE, Form12x.THE_ONE, true); + + public static final Dop ADD_INT_LIT16 = + new Dop(Opcodes.ADD_INT_LIT16, Opcodes.ADD_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop RSUB_INT = + new Dop(Opcodes.RSUB_INT, Opcodes.RSUB_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop MUL_INT_LIT16 = + new Dop(Opcodes.MUL_INT_LIT16, Opcodes.MUL_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop DIV_INT_LIT16 = + new Dop(Opcodes.DIV_INT_LIT16, Opcodes.DIV_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop REM_INT_LIT16 = + new Dop(Opcodes.REM_INT_LIT16, Opcodes.REM_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop AND_INT_LIT16 = + new Dop(Opcodes.AND_INT_LIT16, Opcodes.AND_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop OR_INT_LIT16 = + new Dop(Opcodes.OR_INT_LIT16, Opcodes.OR_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop XOR_INT_LIT16 = + new Dop(Opcodes.XOR_INT_LIT16, Opcodes.XOR_INT, + Opcodes.NO_NEXT, Form22s.THE_ONE, true); + + public static final Dop ADD_INT_LIT8 = + new Dop(Opcodes.ADD_INT_LIT8, Opcodes.ADD_INT, + Opcodes.ADD_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop RSUB_INT_LIT8 = + new Dop(Opcodes.RSUB_INT_LIT8, Opcodes.RSUB_INT, + Opcodes.RSUB_INT, Form22b.THE_ONE, true); + + public static final Dop MUL_INT_LIT8 = + new Dop(Opcodes.MUL_INT_LIT8, Opcodes.MUL_INT, + Opcodes.MUL_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop DIV_INT_LIT8 = + new Dop(Opcodes.DIV_INT_LIT8, Opcodes.DIV_INT, + Opcodes.DIV_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop REM_INT_LIT8 = + new Dop(Opcodes.REM_INT_LIT8, Opcodes.REM_INT, + Opcodes.REM_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop AND_INT_LIT8 = + new Dop(Opcodes.AND_INT_LIT8, Opcodes.AND_INT, + Opcodes.AND_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop OR_INT_LIT8 = + new Dop(Opcodes.OR_INT_LIT8, Opcodes.OR_INT, + Opcodes.OR_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop XOR_INT_LIT8 = + new Dop(Opcodes.XOR_INT_LIT8, Opcodes.XOR_INT, + Opcodes.XOR_INT_LIT16, Form22b.THE_ONE, true); + + public static final Dop SHL_INT_LIT8 = + new Dop(Opcodes.SHL_INT_LIT8, Opcodes.SHL_INT, + Opcodes.NO_NEXT, Form22b.THE_ONE, true); + + public static final Dop SHR_INT_LIT8 = + new Dop(Opcodes.SHR_INT_LIT8, Opcodes.SHR_INT, + Opcodes.NO_NEXT, Form22b.THE_ONE, true); + + public static final Dop USHR_INT_LIT8 = + new Dop(Opcodes.USHR_INT_LIT8, Opcodes.USHR_INT, + Opcodes.NO_NEXT, Form22b.THE_ONE, true); + + public static final Dop INVOKE_POLYMORPHIC = + new Dop(Opcodes.INVOKE_POLYMORPHIC, Opcodes.INVOKE_POLYMORPHIC, + Opcodes.INVOKE_POLYMORPHIC_RANGE, Form45cc.THE_ONE, true); + + public static final Dop INVOKE_POLYMORPHIC_RANGE = + new Dop(Opcodes.INVOKE_POLYMORPHIC_RANGE, Opcodes.INVOKE_POLYMORPHIC, + Opcodes.NO_NEXT, Form4rcc.THE_ONE, true); + + public static final Dop INVOKE_CUSTOM = + new Dop(Opcodes.INVOKE_CUSTOM, Opcodes.INVOKE_CUSTOM, + Opcodes.INVOKE_CUSTOM_RANGE, Form35c.THE_ONE, true); + + public static final Dop INVOKE_CUSTOM_RANGE = + new Dop(Opcodes.INVOKE_CUSTOM_RANGE, Opcodes.INVOKE_CUSTOM, + Opcodes.NO_NEXT, Form3rc.THE_ONE, true); + + // END(dops) + + // Static initialization. + static { + DOPS = new Dop[Opcodes.MAX_VALUE - Opcodes.MIN_VALUE + 1]; + + set(SPECIAL_FORMAT); + + // BEGIN(dops-init); GENERATED AUTOMATICALLY BY opcode-gen + set(NOP); + set(MOVE); + set(MOVE_FROM16); + set(MOVE_16); + set(MOVE_WIDE); + set(MOVE_WIDE_FROM16); + set(MOVE_WIDE_16); + set(MOVE_OBJECT); + set(MOVE_OBJECT_FROM16); + set(MOVE_OBJECT_16); + set(MOVE_RESULT); + set(MOVE_RESULT_WIDE); + set(MOVE_RESULT_OBJECT); + set(MOVE_EXCEPTION); + set(RETURN_VOID); + set(RETURN); + set(RETURN_WIDE); + set(RETURN_OBJECT); + set(CONST_4); + set(CONST_16); + set(CONST); + set(CONST_HIGH16); + set(CONST_WIDE_16); + set(CONST_WIDE_32); + set(CONST_WIDE); + set(CONST_WIDE_HIGH16); + set(CONST_STRING); + set(CONST_STRING_JUMBO); + set(CONST_CLASS); + set(MONITOR_ENTER); + set(MONITOR_EXIT); + set(CHECK_CAST); + set(INSTANCE_OF); + set(ARRAY_LENGTH); + set(NEW_INSTANCE); + set(NEW_ARRAY); + set(FILLED_NEW_ARRAY); + set(FILLED_NEW_ARRAY_RANGE); + set(FILL_ARRAY_DATA); + set(THROW); + set(GOTO); + set(GOTO_16); + set(GOTO_32); + set(PACKED_SWITCH); + set(SPARSE_SWITCH); + set(CMPL_FLOAT); + set(CMPG_FLOAT); + set(CMPL_DOUBLE); + set(CMPG_DOUBLE); + set(CMP_LONG); + set(IF_EQ); + set(IF_NE); + set(IF_LT); + set(IF_GE); + set(IF_GT); + set(IF_LE); + set(IF_EQZ); + set(IF_NEZ); + set(IF_LTZ); + set(IF_GEZ); + set(IF_GTZ); + set(IF_LEZ); + set(AGET); + set(AGET_WIDE); + set(AGET_OBJECT); + set(AGET_BOOLEAN); + set(AGET_BYTE); + set(AGET_CHAR); + set(AGET_SHORT); + set(APUT); + set(APUT_WIDE); + set(APUT_OBJECT); + set(APUT_BOOLEAN); + set(APUT_BYTE); + set(APUT_CHAR); + set(APUT_SHORT); + set(IGET); + set(IGET_WIDE); + set(IGET_OBJECT); + set(IGET_BOOLEAN); + set(IGET_BYTE); + set(IGET_CHAR); + set(IGET_SHORT); + set(IPUT); + set(IPUT_WIDE); + set(IPUT_OBJECT); + set(IPUT_BOOLEAN); + set(IPUT_BYTE); + set(IPUT_CHAR); + set(IPUT_SHORT); + set(SGET); + set(SGET_WIDE); + set(SGET_OBJECT); + set(SGET_BOOLEAN); + set(SGET_BYTE); + set(SGET_CHAR); + set(SGET_SHORT); + set(SPUT); + set(SPUT_WIDE); + set(SPUT_OBJECT); + set(SPUT_BOOLEAN); + set(SPUT_BYTE); + set(SPUT_CHAR); + set(SPUT_SHORT); + set(INVOKE_VIRTUAL); + set(INVOKE_SUPER); + set(INVOKE_DIRECT); + set(INVOKE_STATIC); + set(INVOKE_INTERFACE); + set(INVOKE_VIRTUAL_RANGE); + set(INVOKE_SUPER_RANGE); + set(INVOKE_DIRECT_RANGE); + set(INVOKE_STATIC_RANGE); + set(INVOKE_INTERFACE_RANGE); + set(NEG_INT); + set(NOT_INT); + set(NEG_LONG); + set(NOT_LONG); + set(NEG_FLOAT); + set(NEG_DOUBLE); + set(INT_TO_LONG); + set(INT_TO_FLOAT); + set(INT_TO_DOUBLE); + set(LONG_TO_INT); + set(LONG_TO_FLOAT); + set(LONG_TO_DOUBLE); + set(FLOAT_TO_INT); + set(FLOAT_TO_LONG); + set(FLOAT_TO_DOUBLE); + set(DOUBLE_TO_INT); + set(DOUBLE_TO_LONG); + set(DOUBLE_TO_FLOAT); + set(INT_TO_BYTE); + set(INT_TO_CHAR); + set(INT_TO_SHORT); + set(ADD_INT); + set(SUB_INT); + set(MUL_INT); + set(DIV_INT); + set(REM_INT); + set(AND_INT); + set(OR_INT); + set(XOR_INT); + set(SHL_INT); + set(SHR_INT); + set(USHR_INT); + set(ADD_LONG); + set(SUB_LONG); + set(MUL_LONG); + set(DIV_LONG); + set(REM_LONG); + set(AND_LONG); + set(OR_LONG); + set(XOR_LONG); + set(SHL_LONG); + set(SHR_LONG); + set(USHR_LONG); + set(ADD_FLOAT); + set(SUB_FLOAT); + set(MUL_FLOAT); + set(DIV_FLOAT); + set(REM_FLOAT); + set(ADD_DOUBLE); + set(SUB_DOUBLE); + set(MUL_DOUBLE); + set(DIV_DOUBLE); + set(REM_DOUBLE); + set(ADD_INT_2ADDR); + set(SUB_INT_2ADDR); + set(MUL_INT_2ADDR); + set(DIV_INT_2ADDR); + set(REM_INT_2ADDR); + set(AND_INT_2ADDR); + set(OR_INT_2ADDR); + set(XOR_INT_2ADDR); + set(SHL_INT_2ADDR); + set(SHR_INT_2ADDR); + set(USHR_INT_2ADDR); + set(ADD_LONG_2ADDR); + set(SUB_LONG_2ADDR); + set(MUL_LONG_2ADDR); + set(DIV_LONG_2ADDR); + set(REM_LONG_2ADDR); + set(AND_LONG_2ADDR); + set(OR_LONG_2ADDR); + set(XOR_LONG_2ADDR); + set(SHL_LONG_2ADDR); + set(SHR_LONG_2ADDR); + set(USHR_LONG_2ADDR); + set(ADD_FLOAT_2ADDR); + set(SUB_FLOAT_2ADDR); + set(MUL_FLOAT_2ADDR); + set(DIV_FLOAT_2ADDR); + set(REM_FLOAT_2ADDR); + set(ADD_DOUBLE_2ADDR); + set(SUB_DOUBLE_2ADDR); + set(MUL_DOUBLE_2ADDR); + set(DIV_DOUBLE_2ADDR); + set(REM_DOUBLE_2ADDR); + set(ADD_INT_LIT16); + set(RSUB_INT); + set(MUL_INT_LIT16); + set(DIV_INT_LIT16); + set(REM_INT_LIT16); + set(AND_INT_LIT16); + set(OR_INT_LIT16); + set(XOR_INT_LIT16); + set(ADD_INT_LIT8); + set(RSUB_INT_LIT8); + set(MUL_INT_LIT8); + set(DIV_INT_LIT8); + set(REM_INT_LIT8); + set(AND_INT_LIT8); + set(OR_INT_LIT8); + set(XOR_INT_LIT8); + set(SHL_INT_LIT8); + set(SHR_INT_LIT8); + set(USHR_INT_LIT8); + set(INVOKE_POLYMORPHIC); + set(INVOKE_POLYMORPHIC_RANGE); + set(INVOKE_CUSTOM); + set(INVOKE_CUSTOM_RANGE); + // END(dops-init) + } + + /** + * This class is uninstantiable. + */ + private Dops() { + // This space intentionally left blank. + } + + /** + * Gets the {@link Dop} for the given opcode value. + * + * @param opcode {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the + * opcode value + * @return {@code non-null;} the associated opcode instance + */ + public static Dop get(int opcode) { + int idx = opcode - Opcodes.MIN_VALUE; + + try { + Dop result = DOPS[idx]; + if (result != null) { + return result; + } + } catch (ArrayIndexOutOfBoundsException ex) { + // Fall through. + } + + throw new IllegalArgumentException("bogus opcode"); + } + + /** + * Gets the next {@link Dop} in the instruction fitting chain after the + * given instance, if any. + * + * @param opcode {@code non-null;} the opcode + * @param options {@code non-null;} options, used to determine + * which opcodes are potentially off-limits + * @return {@code null-ok;} the next opcode in the same family, in the + * chain of opcodes to try, or {@code null} if the given opcode is + * the last in its chain + */ + public static Dop getNextOrNull(Dop opcode, DexOptions options) { + int nextOpcode = opcode.getNextOpcode(); + + if (nextOpcode == Opcodes.NO_NEXT) { + return null; + } + + opcode = get(nextOpcode); + + return opcode; + } + + /** + * Puts the given opcode into the table of all ops. + * + * @param opcode {@code non-null;} the opcode + */ + private static void set(Dop opcode) { + int idx = opcode.getOpcode() - Opcodes.MIN_VALUE; + DOPS[idx] = opcode; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/FixedSizeInsn.java b/dexlib/src/main/java/com/android/dx/dex/code/FixedSizeInsn.java new file mode 100644 index 000000000..faed530a4 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/FixedSizeInsn.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.util.AnnotatedOutput; + +/** + * Base class for instructions which are of a fixed code size and + * which use {@link InsnFormat} methods to write themselves. This + * includes most — but not all — instructions. + */ +public abstract class FixedSizeInsn extends DalvInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + *

Note: In the unlikely event that an instruction takes + * absolutely no registers (e.g., a {@code nop} or a + * no-argument no-result * static method call), then the given + * register list may be passed as {@link + * RegisterSpecList#EMPTY}.

+ * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + */ + public FixedSizeInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers) { + super(opcode, position, registers); + } + + /** {@inheritDoc} */ + @Override + public final int codeSize() { + return getOpcode().getFormat().codeSize(); + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(AnnotatedOutput out) { + getOpcode().getFormat().writeTo(out, this); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withRegisterOffset(int delta) { + return withRegisters(getRegisters().withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + protected final String listingString0(boolean noteIndices) { + return getOpcode().getFormat().listingString(this, noteIndices); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/HighRegisterPrefix.java b/dexlib/src/main/java/com/android/dx/dex/code/HighRegisterPrefix.java new file mode 100644 index 000000000..060743e6a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/HighRegisterPrefix.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.util.AnnotatedOutput; + +/** + * Combination instruction which turns into a variable number of + * {@code move*} instructions to move a set of registers into + * registers starting at {@code 0} sequentially. This is used + * in translating an instruction whose register requirements cannot + * be met using a straightforward choice of a single opcode. + */ +public final class HighRegisterPrefix extends VariableSizeInsn { + /** {@code null-ok;} cached instructions, if constructed */ + private SimpleInsn[] insns; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param registers {@code non-null;} source registers + */ + public HighRegisterPrefix(SourcePosition position, + RegisterSpecList registers) { + super(position, registers); + + if (registers.size() == 0) { + throw new IllegalArgumentException("registers.size() == 0"); + } + + insns = null; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + int result = 0; + + calculateInsnsIfNecessary(); + + for (SimpleInsn insn : insns) { + result += insn.codeSize(); + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + calculateInsnsIfNecessary(); + + for (SimpleInsn insn : insns) { + insn.writeTo(out); + } + } + + /** + * Helper for {@link #codeSize} and {@link #writeTo} which sets up + * {@link #insns} if not already done. + */ + private void calculateInsnsIfNecessary() { + if (insns != null) { + return; + } + + RegisterSpecList registers = getRegisters(); + int sz = registers.size(); + + insns = new SimpleInsn[sz]; + + for (int i = 0, outAt = 0; i < sz; i++) { + RegisterSpec src = registers.get(i); + insns[i] = moveInsnFor(src, outAt); + outAt += src.getCategory(); + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new HighRegisterPrefix(getPosition(), registers); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + RegisterSpecList registers = getRegisters(); + int sz = registers.size(); + StringBuffer sb = new StringBuffer(100); + + for (int i = 0, outAt = 0; i < sz; i++) { + RegisterSpec src = registers.get(i); + SimpleInsn insn = moveInsnFor(src, outAt); + + if (i != 0) { + sb.append('\n'); + } + + sb.append(insn.listingString0(noteIndices)); + + outAt += src.getCategory(); + } + + return sb.toString(); + } + + /** + * Returns the proper move instruction for the given source spec + * and destination index. + * + * @param src {@code non-null;} the source register spec + * @param destIndex {@code >= 0;} the destination register index + * @return {@code non-null;} the appropriate move instruction + */ + private static SimpleInsn moveInsnFor(RegisterSpec src, int destIndex) { + return DalvInsn.makeMove(SourcePosition.NO_INFO, + RegisterSpec.make(destIndex, src.getType()), + src); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/InsnFormat.java b/dexlib/src/main/java/com/android/dx/dex/code/InsnFormat.java new file mode 100644 index 000000000..5be0433c8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/InsnFormat.java @@ -0,0 +1,670 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstKnownNull; +import com.android.dx.rop.cst.CstLiteral64; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.BitSet; + +/** + * Base class for all instruction format handlers. Instruction format + * handlers know how to translate {@link DalvInsn} instances into + * streams of code units, as well as human-oriented listing strings + * representing such translations. + */ +public abstract class InsnFormat { + /** + * flag to enable/disable the new extended opcode formats; meant as a + * temporary measure until VM support for the salient opcodes is + * added. TODO: Remove this declaration when the VM can deal. + */ + public static final boolean ALLOW_EXTENDED_OPCODES = true; + + /** + * Returns the string form, suitable for inclusion in a listing + * dump, of the given instruction. The instruction must be of this + * instance's format for proper operation. + * + * @param insn {@code non-null;} the instruction + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code non-null;} the string form + */ + public final String listingString(DalvInsn insn, boolean noteIndices) { + String op = insn.getOpcode().getName(); + String arg = insnArgString(insn); + String comment = insnCommentString(insn, noteIndices); + StringBuilder sb = new StringBuilder(100); + + sb.append(op); + + if (arg.length() != 0) { + sb.append(' '); + sb.append(arg); + } + + if (comment.length() != 0) { + sb.append(" // "); + sb.append(comment); + } + + return sb.toString(); + } + + /** + * Returns the string form of the arguments to the given instruction. + * The instruction must be of this instance's format. If the instruction + * has no arguments, then the result should be {@code ""}, not + * {@code null}. + * + *

Subclasses must override this method.

+ * + * @param insn {@code non-null;} the instruction + * @return {@code non-null;} the string form + */ + public abstract String insnArgString(DalvInsn insn); + + /** + * Returns the associated comment for the given instruction, if any. + * The instruction must be of this instance's format. If the instruction + * has no comment, then the result should be {@code ""}, not + * {@code null}. + * + *

Subclasses must override this method.

+ * + * @param insn {@code non-null;} the instruction + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code non-null;} the string form + */ + public abstract String insnCommentString(DalvInsn insn, + boolean noteIndices); + + /** + * Gets the code size of instructions that use this format. The + * size is a number of 16-bit code units, not bytes. This should + * throw an exception if this format is of variable size. + * + * @return {@code >= 0;} the instruction length in 16-bit code units + */ + public abstract int codeSize(); + + /** + * Returns whether or not the given instruction's arguments will + * fit in this instance's format. This includes such things as + * counting register arguments, checking register ranges, and + * making sure that additional arguments are of appropriate types + * and are in-range. If this format has a branch target but the + * instruction's branch offset is unknown, this method will simply + * not check the offset. + * + *

Subclasses must override this method.

+ * + * @param insn {@code non-null;} the instruction to check + * @return {@code true} iff the instruction's arguments are + * appropriate for this instance, or {@code false} if not + */ + public abstract boolean isCompatible(DalvInsn insn); + + /** + * Returns which of a given instruction's registers will fit in + * this instance's format. + * + *

The default implementation of this method always returns + * an empty BitSet. Subclasses must override this method if they + * have registers.

+ * + * @param insn {@code non-null;} the instruction to check + * @return {@code non-null;} a BitSet flagging registers in the + * register list that are compatible to this format + */ + public BitSet compatibleRegs(DalvInsn insn) { + return new BitSet(); + } + + /** + * Returns whether or not the given instruction's branch offset will + * fit in this instance's format. This always returns {@code false} + * for formats that don't include a branch offset. + * + *

The default implementation of this method always returns + * {@code false}. Subclasses must override this method if they + * include branch offsets.

+ * + * @param insn {@code non-null;} the instruction to check + * @return {@code true} iff the instruction's branch offset is + * appropriate for this instance, or {@code false} if not + */ + public boolean branchFits(TargetInsn insn) { + return false; + } + + /** + * Writes the code units for the given instruction to the given + * output destination. The instruction must be of this instance's format. + * + *

Subclasses must override this method.

+ * + * @param out {@code non-null;} the output destination to write to + * @param insn {@code non-null;} the instruction to write + */ + public abstract void writeTo(AnnotatedOutput out, DalvInsn insn); + + /** + * Helper method to return a register list string. + * + * @param list {@code non-null;} the list of registers + * @return {@code non-null;} the string form + */ + protected static String regListString(RegisterSpecList list) { + int sz = list.size(); + StringBuffer sb = new StringBuffer(sz * 5 + 2); + + sb.append('{'); + + for (int i = 0; i < sz; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(list.get(i).regString()); + } + + sb.append('}'); + + return sb.toString(); + } + + /** + * Helper method to return a register range string. + * + * @param list {@code non-null;} the list of registers (which must be + * sequential) + * @return {@code non-null;} the string form + */ + protected static String regRangeString(RegisterSpecList list) { + int size = list.size(); + StringBuilder sb = new StringBuilder(30); + + sb.append("{"); + + switch (size) { + case 0: { + // Nothing to do. + break; + } + case 1: { + sb.append(list.get(0).regString()); + break; + } + default: { + RegisterSpec lastReg = list.get(size - 1); + if (lastReg.getCategory() == 2) { + /* + * Add one to properly represent a list-final + * category-2 register. + */ + lastReg = lastReg.withOffset(1); + } + + sb.append(list.get(0).regString()); + sb.append(".."); + sb.append(lastReg.regString()); + } + } + + sb.append("}"); + + return sb.toString(); + } + + /** + * Helper method to return a literal bits argument string. + * + * @param value the value + * @return {@code non-null;} the string form + */ + protected static String literalBitsString(CstLiteralBits value) { + StringBuffer sb = new StringBuffer(100); + + sb.append('#'); + + if (value instanceof CstKnownNull) { + sb.append("null"); + } else { + sb.append(value.typeName()); + sb.append(' '); + sb.append(value.toHuman()); + } + + return sb.toString(); + } + + /** + * Helper method to return a literal bits comment string. + * + * @param value the value + * @param width the width of the constant, in bits (used for displaying + * the uninterpreted bits; one of: {@code 4 8 16 32 64} + * @return {@code non-null;} the comment + */ + protected static String literalBitsComment(CstLiteralBits value, + int width) { + StringBuffer sb = new StringBuffer(20); + + sb.append("#"); + + long bits; + + if (value instanceof CstLiteral64) { + bits = ((CstLiteral64) value).getLongBits(); + } else { + bits = value.getIntBits(); + } + + switch (width) { + case 4: sb.append(Hex.uNibble((int) bits)); break; + case 8: sb.append(Hex.u1((int) bits)); break; + case 16: sb.append(Hex.u2((int) bits)); break; + case 32: sb.append(Hex.u4((int) bits)); break; + case 64: sb.append(Hex.u8(bits)); break; + default: { + throw new RuntimeException("shouldn't happen"); + } + } + + return sb.toString(); + } + + /** + * Helper method to return a branch address string. + * + * @param insn {@code non-null;} the instruction in question + * @return {@code non-null;} the string form of the instruction's + * branch target + */ + protected static String branchString(DalvInsn insn) { + TargetInsn ti = (TargetInsn) insn; + int address = ti.getTargetAddress(); + + return (address == (char) address) ? Hex.u2(address) : Hex.u4(address); + } + + /** + * Helper method to return the comment for a branch. + * + * @param insn {@code non-null;} the instruction in question + * @return {@code non-null;} the comment + */ + protected static String branchComment(DalvInsn insn) { + TargetInsn ti = (TargetInsn) insn; + int offset = ti.getTargetOffset(); + + return (offset == (short) offset) ? Hex.s2(offset) : Hex.s4(offset); + } + + /** + * Helper method to determine if a signed int value fits in a nibble. + * + * @param value the value in question + * @return {@code true} iff it's in the range -8..+7 + */ + protected static boolean signedFitsInNibble(int value) { + return (value >= -8) && (value <= 7); + } + + /** + * Helper method to determine if an unsigned int value fits in a nibble. + * + * @param value the value in question + * @return {@code true} iff it's in the range 0..0xf + */ + protected static boolean unsignedFitsInNibble(int value) { + return value == (value & 0xf); + } + + /** + * Helper method to determine if a signed int value fits in a byte. + * + * @param value the value in question + * @return {@code true} iff it's in the range -0x80..+0x7f + */ + protected static boolean signedFitsInByte(int value) { + return (byte) value == value; + } + + /** + * Helper method to determine if an unsigned int value fits in a byte. + * + * @param value the value in question + * @return {@code true} iff it's in the range 0..0xff + */ + protected static boolean unsignedFitsInByte(int value) { + return value == (value & 0xff); + } + + /** + * Helper method to determine if a signed int value fits in a short. + * + * @param value the value in question + * @return {@code true} iff it's in the range -0x8000..+0x7fff + */ + protected static boolean signedFitsInShort(int value) { + return (short) value == value; + } + + /** + * Helper method to determine if an unsigned int value fits in a short. + * + * @param value the value in question + * @return {@code true} iff it's in the range 0..0xffff + */ + protected static boolean unsignedFitsInShort(int value) { + return value == (value & 0xffff); + } + + /** + * Helper method to determine if a list of registers are sequential, + * including degenerate cases for empty or single-element lists. + * + * @param list {@code non-null;} the list of registers + * @return {@code true} iff the list is sequentially ordered + */ + protected static boolean isRegListSequential(RegisterSpecList list) { + int sz = list.size(); + + if (sz < 2) { + return true; + } + + int first = list.get(0).getReg(); + int next = first; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = list.get(i); + if (one.getReg() != next) { + return false; + } + next += one.getCategory(); + } + + return true; + } + + /** + * Helper method to extract the callout-argument index from an + * appropriate instruction. + * + * @param insn {@code non-null;} the instruction + * @return {@code >= 0;} the callout argument index + */ + protected static int argIndex(DalvInsn insn) { + int arg = ((CstInteger) ((CstInsn) insn).getConstant()).getValue(); + + if (arg < 0) { + throw new IllegalArgumentException("bogus insn"); + } + + return arg; + } + + /** + * Helper method to combine an opcode and a second byte of data into + * the appropriate form for emitting into a code buffer. + * + * @param insn {@code non-null;} the instruction containing the opcode + * @param arg {@code 0..255;} arbitrary other byte value + * @return combined value + */ + protected static short opcodeUnit(DalvInsn insn, int arg) { + if ((arg & 0xff) != arg) { + throw new IllegalArgumentException("arg out of range 0..255"); + } + + int opcode = insn.getOpcode().getOpcode(); + + if ((opcode & 0xff) != opcode) { + throw new IllegalArgumentException("opcode out of range 0..255"); + } + + return (short) (opcode | (arg << 8)); + } + + /** + * Helper method to get an extended (16-bit) opcode out of an + * instruction, returning it as a code unit. The opcode + * must be an extended opcode. + * + * @param insn {@code non-null;} the instruction containing the + * extended opcode + * @return the opcode as a code unit + */ + protected static short opcodeUnit(DalvInsn insn) { + int opcode = insn.getOpcode().getOpcode(); + + if ((opcode < 0x100) || (opcode > 0xffff)) { + throw new IllegalArgumentException("opcode out of range 0..65535"); + } + + return (short) opcode; + } + + /** + * Helper method to combine two bytes into a code unit. + * + * @param low {@code 0..255;} low byte + * @param high {@code 0..255;} high byte + * @return combined value + */ + protected static short codeUnit(int low, int high) { + if ((low & 0xff) != low) { + throw new IllegalArgumentException("low out of range 0..255"); + } + + if ((high & 0xff) != high) { + throw new IllegalArgumentException("high out of range 0..255"); + } + + return (short) (low | (high << 8)); + } + + /** + * Helper method to combine four nibbles into a code unit. + * + * @param n0 {@code 0..15;} low nibble + * @param n1 {@code 0..15;} medium-low nibble + * @param n2 {@code 0..15;} medium-high nibble + * @param n3 {@code 0..15;} high nibble + * @return combined value + */ + protected static short codeUnit(int n0, int n1, int n2, int n3) { + if ((n0 & 0xf) != n0) { + throw new IllegalArgumentException("n0 out of range 0..15"); + } + + if ((n1 & 0xf) != n1) { + throw new IllegalArgumentException("n1 out of range 0..15"); + } + + if ((n2 & 0xf) != n2) { + throw new IllegalArgumentException("n2 out of range 0..15"); + } + + if ((n3 & 0xf) != n3) { + throw new IllegalArgumentException("n3 out of range 0..15"); + } + + return (short) (n0 | (n1 << 4) | (n2 << 8) | (n3 << 12)); + } + + /** + * Helper method to combine two nibbles into a byte. + * + * @param low {@code 0..15;} low nibble + * @param high {@code 0..15;} high nibble + * @return {@code 0..255;} combined value + */ + protected static int makeByte(int low, int high) { + if ((low & 0xf) != low) { + throw new IllegalArgumentException("low out of range 0..15"); + } + + if ((high & 0xf) != high) { + throw new IllegalArgumentException("high out of range 0..15"); + } + + return low | (high << 4); + } + + /** + * Writes one code unit to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0) { + out.writeShort(c0); + } + + /** + * Writes two code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1) { + out.writeShort(c0); + out.writeShort(c1); + } + + /** + * Writes three code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + } + + /** + * Writes four code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + * @param c3 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2, short c3) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + out.writeShort(c3); + } + + /** + * Writes five code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + * @param c3 code unit to write + * @param c4 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2, short c3, short c4) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + out.writeShort(c3); + out.writeShort(c4); + } + + /** + * Writes three code units to the given output destination, where the + * second and third are represented as single int and emitted + * in little-endian order. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1c2 code unit pair to write + */ + protected static void write(AnnotatedOutput out, short c0, int c1c2) { + write(out, c0, (short) c1c2, (short) (c1c2 >> 16)); + } + + /** + * Writes four code units to the given output destination, where the + * second and third are represented as single int and emitted + * in little-endian order. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1c2 code unit pair to write + * @param c3 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, int c1c2, + short c3) { + write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3); + } + + /** + * Writes five code units to the given output destination, where the + * second and third are represented as single int and emitted + * in little-endian order. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1c2 code unit pair to write + * @param c3 code unit to write + * @param c4 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, int c1c2, + short c3, short c4) { + write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3, c4); + } + + /** + * Writes five code units to the given output destination, where the + * second through fifth are represented as single long + * and emitted in little-endian order. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1c2c3c4 code unit quad to write + */ + protected static void write(AnnotatedOutput out, short c0, long c1c2c3c4) { + write(out, c0, (short) c1c2c3c4, (short) (c1c2c3c4 >> 16), + (short) (c1c2c3c4 >> 32), (short) (c1c2c3c4 >> 48)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/LocalList.java b/dexlib/src/main/java/com/android/dx/dex/code/LocalList.java new file mode 100644 index 000000000..31e3fd080 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/LocalList.java @@ -0,0 +1,944 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecSet; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.util.FixedSizeList; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * List of local variables. Each local variable entry indicates a + * range of code which it is valid for, a register number, a name, + * and a type. + */ +public final class LocalList extends FixedSizeList { + /** {@code non-null;} empty instance */ + public static final LocalList EMPTY = new LocalList(0); + + /** whether to run the self-check code */ + private static final boolean DEBUG = false; + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the list + */ + public LocalList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintStream out, String prefix) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + out.print(prefix); + out.println(get(i)); + } + } + + /** + * Disposition of a local entry. + */ + public static enum Disposition { + /** local started (introduced) */ + START, + + /** local ended without being replaced */ + END_SIMPLY, + + /** local ended because it was directly replaced */ + END_REPLACED, + + /** local ended because it was moved to a different register */ + END_MOVED, + + /** + * local ended because the previous local clobbered this one + * (because it is category-2) + */ + END_CLOBBERED_BY_PREV, + + /** + * local ended because the next local clobbered this one + * (because this one is a category-2) + */ + END_CLOBBERED_BY_NEXT; + } + + /** + * Entry in a local list. + */ + public static class Entry implements Comparable { + /** {@code >= 0;} address */ + private final int address; + + /** {@code non-null;} disposition of the local */ + private final Disposition disposition; + + /** {@code non-null;} register spec representing the variable */ + private final RegisterSpec spec; + + /** {@code non-null;} variable type (derived from {@code spec}) */ + private final CstType type; + + /** + * Constructs an instance. + * + * @param address {@code >= 0;} address + * @param disposition {@code non-null;} disposition of the local + * @param spec {@code non-null;} register spec representing + * the variable + */ + public Entry(int address, Disposition disposition, RegisterSpec spec) { + if (address < 0) { + throw new IllegalArgumentException("address < 0"); + } + + if (disposition == null) { + throw new NullPointerException("disposition == null"); + } + + try { + if (spec.getLocalItem() == null) { + throw new NullPointerException( + "spec.getLocalItem() == null"); + } + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("spec == null"); + } + + this.address = address; + this.disposition = disposition; + this.spec = spec; + this.type = CstType.intern(spec.getType()); + } + + /** {@inheritDoc} */ + public String toString() { + return Integer.toHexString(address) + " " + disposition + " " + + spec; + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (!(other instanceof Entry)) { + return false; + } + + return (compareTo((Entry) other) == 0); + } + + /** + * Compares by (in priority order) address, end then start + * disposition (variants of end are all consistered + * equivalent), and spec. + * + * @param other {@code non-null;} entry to compare to + * @return {@code -1..1;} standard result of comparison + */ + public int compareTo(Entry other) { + if (address < other.address) { + return -1; + } else if (address > other.address) { + return 1; + } + + boolean thisIsStart = isStart(); + boolean otherIsStart = other.isStart(); + + if (thisIsStart != otherIsStart) { + return thisIsStart ? 1 : -1; + } + + return spec.compareTo(other.spec); + } + + /** + * Gets the address. + * + * @return {@code >= 0;} the address + */ + public int getAddress() { + return address; + } + + /** + * Gets the disposition. + * + * @return {@code non-null;} the disposition + */ + public Disposition getDisposition() { + return disposition; + } + + /** + * Gets whether this is a local start. This is just shorthand for + * {@code getDisposition() == Disposition.START}. + * + * @return {@code true} iff this is a start + */ + public boolean isStart() { + return disposition == Disposition.START; + } + + /** + * Gets the variable name. + * + * @return {@code null-ok;} the variable name + */ + public CstString getName() { + return spec.getLocalItem().getName(); + } + + /** + * Gets the variable signature. + * + * @return {@code null-ok;} the variable signature + */ + public CstString getSignature() { + return spec.getLocalItem().getSignature(); + } + + /** + * Gets the variable's type. + * + * @return {@code non-null;} the type + */ + public CstType getType() { + return type; + } + + /** + * Gets the number of the register holding the variable. + * + * @return {@code >= 0;} the number of the register holding + * the variable + */ + public int getRegister() { + return spec.getReg(); + } + + /** + * Gets the RegisterSpec of the register holding the variable. + * + * @return {@code non-null;} RegisterSpec of the holding register. + */ + public RegisterSpec getRegisterSpec() { + return spec; + } + + /** + * Returns whether or not this instance matches the given spec. + * + * @param otherSpec {@code non-null;} the spec in question + * @return {@code true} iff this instance matches + * {@code spec} + */ + public boolean matches(RegisterSpec otherSpec) { + return spec.equalsUsingSimpleType(otherSpec); + } + + /** + * Returns whether or not this instance matches the spec in + * the given instance. + * + * @param other {@code non-null;} another entry + * @return {@code true} iff this instance's spec matches + * {@code other} + */ + public boolean matches(Entry other) { + return matches(other.spec); + } + + /** + * Returns an instance just like this one but with the disposition + * set as given. + * + * @param disposition {@code non-null;} the new disposition + * @return {@code non-null;} an appropriately-constructed instance + */ + public Entry withDisposition(Disposition disposition) { + if (disposition == this.disposition) { + return this; + } + + return new Entry(address, disposition, spec); + } + } + + /** + * Constructs an instance for the given method, based on the given + * block order and intermediate local information. + * + * @param insns {@code non-null;} instructions to convert + * @return {@code non-null;} the constructed list + */ + public static LocalList make(DalvInsnList insns) { + int sz = insns.size(); + + /* + * Go through the insn list, looking for all the local + * variable pseudoinstructions, splitting out LocalSnapshots + * into separate per-variable starts, adding explicit ends + * wherever a variable is replaced or moved, and collecting + * these and all the other local variable "activity" + * together into an output list (without the other insns). + * + * Note: As of this writing, this method won't be handed any + * insn lists that contain local ends, but I (danfuzz) expect + * that to change at some point, when we start feeding that + * info explicitly into the rop layer rather than only trying + * to infer it. So, given that expectation, this code is + * written to deal with them. + */ + + MakeState state = new MakeState(sz); + + for (int i = 0; i < sz; i++) { + DalvInsn insn = insns.get(i); + + if (insn instanceof LocalSnapshot) { + RegisterSpecSet snapshot = + ((LocalSnapshot) insn).getLocals(); + state.snapshot(insn.getAddress(), snapshot); + } else if (insn instanceof LocalStart) { + RegisterSpec local = ((LocalStart) insn).getLocal(); + state.startLocal(insn.getAddress(), local); + } + } + + LocalList result = state.finish(); + + if (DEBUG) { + debugVerify(result); + } + + return result; + } + + /** + * Debugging helper that verifies the constraint that a list doesn't + * contain any redundant local starts and that local ends that are + * due to replacements are properly annotated. + */ + private static void debugVerify(LocalList locals) { + try { + debugVerify0(locals); + } catch (RuntimeException ex) { + int sz = locals.size(); + for (int i = 0; i < sz; i++) { + System.err.println(locals.get(i)); + } + throw ex; + } + + } + + /** + * Helper for {@link #debugVerify} which does most of the work. + */ + private static void debugVerify0(LocalList locals) { + int sz = locals.size(); + Entry[] active = new Entry[65536]; + + for (int i = 0; i < sz; i++) { + Entry e = locals.get(i); + int reg = e.getRegister(); + + if (e.isStart()) { + Entry already = active[reg]; + + if ((already != null) && e.matches(already)) { + throw new RuntimeException("redundant start at " + + Integer.toHexString(e.getAddress()) + ": got " + + e + "; had " + already); + } + + active[reg] = e; + } else { + if (active[reg] == null) { + throw new RuntimeException("redundant end at " + + Integer.toHexString(e.getAddress())); + } + + int addr = e.getAddress(); + boolean foundStart = false; + + for (int j = i + 1; j < sz; j++) { + Entry test = locals.get(j); + if (test.getAddress() != addr) { + break; + } + if (test.getRegisterSpec().getReg() == reg) { + if (test.isStart()) { + if (e.getDisposition() + != Disposition.END_REPLACED) { + throw new RuntimeException( + "improperly marked end at " + + Integer.toHexString(addr)); + } + foundStart = true; + } else { + throw new RuntimeException( + "redundant end at " + + Integer.toHexString(addr)); + } + } + } + + if (!foundStart && + (e.getDisposition() == Disposition.END_REPLACED)) { + throw new RuntimeException( + "improper end replacement claim at " + + Integer.toHexString(addr)); + } + + active[reg] = null; + } + } + } + + /** + * Intermediate state when constructing a local list. + */ + public static class MakeState { + /** {@code non-null;} result being collected */ + private final ArrayList result; + + /** + * {@code >= 0;} running count of nulled result entries, to help with + * sizing the final list + */ + private int nullResultCount; + + /** {@code null-ok;} current register mappings */ + private RegisterSpecSet regs; + + /** {@code null-ok;} result indices where local ends are stored */ + private int[] endIndices; + + /** {@code >= 0;} last address seen */ + private int lastAddress; + + /** + * Constructs an instance. + */ + public MakeState(int initialSize) { + result = new ArrayList(initialSize); + nullResultCount = 0; + regs = null; + endIndices = null; + lastAddress = 0; + } + + /** + * Checks the address and other vitals as a prerequisite to + * further processing. + * + * @param address {@code >= 0;} address about to be processed + * @param reg {@code >= 0;} register number about to be processed + */ + private void aboutToProcess(int address, int reg) { + boolean first = (endIndices == null); + + if ((address == lastAddress) && !first) { + return; + } + + if (address < lastAddress) { + throw new RuntimeException("shouldn't happen"); + } + + if (first || (reg >= endIndices.length)) { + /* + * This is the first allocation of the state set and + * index array, or we need to grow. (The latter doesn't + * happen much; in fact, we have only ever observed + * it happening in test cases, never in "real" code.) + */ + int newSz = reg + 1; + RegisterSpecSet newRegs = new RegisterSpecSet(newSz); + int[] newEnds = new int[newSz]; + Arrays.fill(newEnds, -1); + + if (!first) { + newRegs.putAll(regs); + System.arraycopy(endIndices, 0, newEnds, 0, + endIndices.length); + } + + regs = newRegs; + endIndices = newEnds; + } + } + + /** + * Sets the local state at the given address to the given snapshot. + * The first call on this instance must be to this method, so that + * the register state can be properly sized. + * + * @param address {@code >= 0;} the address + * @param specs {@code non-null;} spec set representing the locals + */ + public void snapshot(int address, RegisterSpecSet specs) { + if (DEBUG) { + System.err.printf("%04x snapshot %s\n", address, specs); + } + + int sz = specs.getMaxSize(); + aboutToProcess(address, sz - 1); + + for (int i = 0; i < sz; i++) { + RegisterSpec oldSpec = regs.get(i); + RegisterSpec newSpec = filterSpec(specs.get(i)); + + if (oldSpec == null) { + if (newSpec != null) { + startLocal(address, newSpec); + } + } else if (newSpec == null) { + endLocal(address, oldSpec); + } else if (! newSpec.equalsUsingSimpleType(oldSpec)) { + endLocal(address, oldSpec); + startLocal(address, newSpec); + } + } + + if (DEBUG) { + System.err.printf("%04x snapshot done\n", address); + } + } + + /** + * Starts a local at the given address. + * + * @param address {@code >= 0;} the address + * @param startedLocal {@code non-null;} spec representing the + * started local + */ + public void startLocal(int address, RegisterSpec startedLocal) { + if (DEBUG) { + System.err.printf("%04x start %s\n", address, startedLocal); + } + + int regNum = startedLocal.getReg(); + + startedLocal = filterSpec(startedLocal); + aboutToProcess(address, regNum); + + RegisterSpec existingLocal = regs.get(regNum); + + if (startedLocal.equalsUsingSimpleType(existingLocal)) { + // Silently ignore a redundant start. + return; + } + + RegisterSpec movedLocal = regs.findMatchingLocal(startedLocal); + if (movedLocal != null) { + /* + * The same variable was moved from one register to another. + * So add an end for its old location. + */ + addOrUpdateEnd(address, Disposition.END_MOVED, movedLocal); + } + + int endAt = endIndices[regNum]; + + if (existingLocal != null) { + /* + * There is an existing (but non-matching) local. + * Add an explicit end for it. + */ + add(address, Disposition.END_REPLACED, existingLocal); + } else if (endAt >= 0) { + /* + * Look for an end local for the same register at the + * same address. If found, then update it or delete + * it, depending on whether or not it represents the + * same variable as the one being started. + */ + Entry endEntry = result.get(endAt); + if (endEntry.getAddress() == address) { + if (endEntry.matches(startedLocal)) { + /* + * There was already an end local for the same + * variable at the same address. This turns + * out to be superfluous, as we are starting + * up the exact same local. This situation can + * happen when a single local variable got + * somehow "split up" during intermediate + * processing. In any case, rather than represent + * the end-then-start, just remove the old end. + */ + result.set(endAt, null); + nullResultCount++; + regs.put(startedLocal); + endIndices[regNum] = -1; + return; + } else { + /* + * There was a different variable ended at the + * same address. Update it to indicate that + * it was ended due to a replacement (rather than + * ending for no particular reason). + */ + endEntry = endEntry.withDisposition( + Disposition.END_REPLACED); + result.set(endAt, endEntry); + } + } + } + + /* + * The code above didn't find and remove an unnecessary + * local end, so we now have to add one or more entries to + * the output to capture the transition. + */ + + /* + * If the local just below (in the register set at reg-1) + * is of category-2, then it is ended by this new start. + */ + if (regNum > 0) { + RegisterSpec justBelow = regs.get(regNum - 1); + if ((justBelow != null) && justBelow.isCategory2()) { + addOrUpdateEnd(address, + Disposition.END_CLOBBERED_BY_NEXT, + justBelow); + } + } + + /* + * Similarly, if this local is category-2, then the local + * just above (if any) is ended by the start now being + * emitted. + */ + if (startedLocal.isCategory2()) { + RegisterSpec justAbove = regs.get(regNum + 1); + if (justAbove != null) { + addOrUpdateEnd(address, + Disposition.END_CLOBBERED_BY_PREV, + justAbove); + } + } + + /* + * TODO: Add an end for the same local in a different reg, + * if any (that is, if the local migrates from vX to vY, + * we should note that as a local end in vX). + */ + + add(address, Disposition.START, startedLocal); + } + + /** + * Ends a local at the given address, using the disposition + * {@code END_SIMPLY}. + * + * @param address {@code >= 0;} the address + * @param endedLocal {@code non-null;} spec representing the + * local being ended + */ + public void endLocal(int address, RegisterSpec endedLocal) { + endLocal(address, endedLocal, Disposition.END_SIMPLY); + } + + /** + * Ends a local at the given address. + * + * @param address {@code >= 0;} the address + * @param endedLocal {@code non-null;} spec representing the + * local being ended + * @param disposition reason for the end + */ + public void endLocal(int address, RegisterSpec endedLocal, + Disposition disposition) { + if (DEBUG) { + System.err.printf("%04x end %s\n", address, endedLocal); + } + + int regNum = endedLocal.getReg(); + + endedLocal = filterSpec(endedLocal); + aboutToProcess(address, regNum); + + int endAt = endIndices[regNum]; + + if (endAt >= 0) { + /* + * The local in the given register is already ended. + * Silently return without adding anything to the result. + */ + return; + } + + // Check for start and end at the same address. + if (checkForEmptyRange(address, endedLocal)) { + return; + } + + add(address, disposition, endedLocal); + } + + /** + * Helper for {@link #endLocal}, which handles the cases where + * and end local is issued at the same address as a start local + * for the same register. If this case is found, then this + * method will remove the start (as the local was never actually + * active), update the {@link #endIndices} to be accurate, and + * if needed update the newly-active end to reflect an altered + * disposition. + * + * @param address {@code >= 0;} the address + * @param endedLocal {@code non-null;} spec representing the + * local being ended + * @return {@code true} iff this method found the case in question + * and adjusted things accordingly + */ + private boolean checkForEmptyRange(int address, + RegisterSpec endedLocal) { + int at = result.size() - 1; + Entry entry; + + // Look for a previous entry at the same address. + for (/*at*/; at >= 0; at--) { + entry = result.get(at); + + if (entry == null) { + continue; + } + + if (entry.getAddress() != address) { + // We didn't find any match at the same address. + return false; + } + + if (entry.matches(endedLocal)) { + break; + } + } + + /* + * In fact, we found that the endedLocal had started at the + * same address, so do all the requisite cleanup. + */ + + regs.remove(endedLocal); + result.set(at, null); + nullResultCount++; + + int regNum = endedLocal.getReg(); + boolean found = false; + entry = null; + + // Now look back further to update where the register ended. + for (at--; at >= 0; at--) { + entry = result.get(at); + + if (entry == null) { + continue; + } + + if (entry.getRegisterSpec().getReg() == regNum) { + found = true; + break; + } + } + + if (found) { + // We found an end for the same register. + endIndices[regNum] = at; + + if (entry.getAddress() == address) { + /* + * It's still the same address, so update the + * disposition. + */ + result.set(at, + entry.withDisposition(Disposition.END_SIMPLY)); + } + } + + return true; + } + + /** + * Converts a given spec into the form acceptable for use in a + * local list. This, in particular, transforms the "known + * null" type into simply {@code Object}. This method needs to + * be called for any spec that is on its way into a locals + * list. + * + *

This isn't necessarily the cleanest way to achieve the + * goal of not representing known nulls in a locals list, but + * it gets the job done.

+ * + * @param orig {@code null-ok;} the original spec + * @return {@code null-ok;} an appropriately modified spec, or the + * original if nothing needs to be done + */ + private static RegisterSpec filterSpec(RegisterSpec orig) { + if ((orig != null) && (orig.getType() == Type.KNOWN_NULL)) { + return orig.withType(Type.OBJECT); + } + + return orig; + } + + /** + * Adds an entry to the result, updating the adjunct tables + * accordingly. + * + * @param address {@code >= 0;} the address + * @param disposition {@code non-null;} the disposition + * @param spec {@code non-null;} spec representing the local + */ + private void add(int address, Disposition disposition, + RegisterSpec spec) { + int regNum = spec.getReg(); + + result.add(new Entry(address, disposition, spec)); + + if (disposition == Disposition.START) { + regs.put(spec); + endIndices[regNum] = -1; + } else { + regs.remove(spec); + endIndices[regNum] = result.size() - 1; + } + } + + /** + * Adds or updates an end local (changing its disposition). If + * this would cause an empty range for a local, this instead + * removes the local entirely. + * + * @param address {@code >= 0;} the address + * @param disposition {@code non-null;} the disposition + * @param spec {@code non-null;} spec representing the local + */ + private void addOrUpdateEnd(int address, Disposition disposition, + RegisterSpec spec) { + if (disposition == Disposition.START) { + throw new RuntimeException("shouldn't happen"); + } + + int regNum = spec.getReg(); + int endAt = endIndices[regNum]; + + if (endAt >= 0) { + // There is a previous end. + Entry endEntry = result.get(endAt); + if ((endEntry.getAddress() == address) && + endEntry.getRegisterSpec().equals(spec)) { + /* + * The end is for the right address and variable, so + * update it. + */ + result.set(endAt, endEntry.withDisposition(disposition)); + regs.remove(spec); // TODO: Is this line superfluous? + return; + } + } + + endLocal(address, spec, disposition); + } + + /** + * Finishes processing altogether and gets the result. + * + * @return {@code non-null;} the result list + */ + public LocalList finish() { + aboutToProcess(Integer.MAX_VALUE, 0); + + int resultSz = result.size(); + int finalSz = resultSz - nullResultCount; + + if (finalSz == 0) { + return EMPTY; + } + + /* + * Collect an array of only the non-null entries, and then + * sort it to get a consistent order for everything: Local + * ends and starts for a given address could come in any + * order, but we want ends before starts as well as + * registers in order (within ends or starts). + */ + + Entry[] resultArr = new Entry[finalSz]; + + if (resultSz == finalSz) { + result.toArray(resultArr); + } else { + int at = 0; + for (Entry e : result) { + if (e != null) { + resultArr[at++] = e; + } + } + } + + Arrays.sort(resultArr); + + LocalList resultList = new LocalList(finalSz); + + for (int i = 0; i < finalSz; i++) { + resultList.set(i, resultArr[i]); + } + + resultList.setImmutable(); + return resultList; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/LocalSnapshot.java b/dexlib/src/main/java/com/android/dx/dex/code/LocalSnapshot.java new file mode 100644 index 000000000..863a0ef5c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/LocalSnapshot.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.RegisterSpecSet; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.ssa.RegisterMapper; + +/** + * Pseudo-instruction which is used to hold a snapshot of the + * state of local variable name mappings that exists immediately after + * the instance in an instruction array. + */ +public final class LocalSnapshot extends ZeroSizeInsn { + /** {@code non-null;} local state associated with this instance */ + private final RegisterSpecSet locals; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param locals {@code non-null;} associated local variable state + */ + public LocalSnapshot(SourcePosition position, RegisterSpecSet locals) { + super(position); + + if (locals == null) { + throw new NullPointerException("locals == null"); + } + + this.locals = locals; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return new LocalSnapshot(getPosition(), locals.withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new LocalSnapshot(getPosition(), locals); + } + + /** + * Gets the local state associated with this instance. + * + * @return {@code non-null;} the state + */ + public RegisterSpecSet getLocals() { + return locals; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return locals.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + int sz = locals.size(); + int max = locals.getMaxSize(); + StringBuffer sb = new StringBuffer(100 + sz * 40); + + sb.append("local-snapshot"); + + for (int i = 0; i < max; i++) { + RegisterSpec spec = locals.get(i); + if (spec != null) { + sb.append("\n "); + sb.append(LocalStart.localString(spec)); + } + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withMapper(RegisterMapper mapper) { + return new LocalSnapshot(getPosition(), mapper.map(locals)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/LocalStart.java b/dexlib/src/main/java/com/android/dx/dex/code/LocalStart.java new file mode 100644 index 000000000..0566cb515 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/LocalStart.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.ssa.RegisterMapper; + +/** + * Pseudo-instruction which is used to introduce a new local variable. That + * is, an instance of this class in an instruction stream indicates that + * starting with the subsequent instruction, the indicated variable + * is bound. + */ +public final class LocalStart extends ZeroSizeInsn { + /** + * {@code non-null;} register spec representing the local variable introduced + * by this instance + */ + private final RegisterSpec local; + + /** + * Returns the local variable listing string for a single register spec. + * + * @param spec {@code non-null;} the spec to convert + * @return {@code non-null;} the string form + */ + public static String localString(RegisterSpec spec) { + return spec.regString() + ' ' + spec.getLocalItem().toString() + ": " + + spec.getTypeBearer().toHuman(); + } + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param local {@code non-null;} register spec representing the local + * variable introduced by this instance + */ + public LocalStart(SourcePosition position, RegisterSpec local) { + super(position); + + if (local == null) { + throw new NullPointerException("local == null"); + } + + this.local = local; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return new LocalStart(getPosition(), local.withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new LocalStart(getPosition(), local); + } + + /** + * Gets the register spec representing the local variable introduced + * by this instance. + * + * @return {@code non-null;} the register spec + */ + public RegisterSpec getLocal() { + return local; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return local.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + return "local-start " + localString(local); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withMapper(RegisterMapper mapper) { + return new LocalStart(getPosition(), mapper.map(local)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/MultiCstInsn.java b/dexlib/src/main/java/com/android/dx/dex/code/MultiCstInsn.java new file mode 100644 index 000000000..5b62a2ce0 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/MultiCstInsn.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.cst.Constant; +import com.android.dx.util.Hex; + +/** + * An instruction with multiple constant arguments in addition + * to all the normal instruction information. + */ +public final class MultiCstInsn extends FixedSizeInsn { + private static final int NOT_SET = -1; + + /** {@code non-null;} the constant argument for this instruction */ + private final Constant[] constants; + + /** + * {@code >= NOT_SET;} the constant pool indicies for {@link #constant}, or + * {@code NOT_SET} if not yet set + */ + private final int[] index; + + /** + * {@code >= NOT_SET;} the constant pool index for the class reference in + * {@link #constant} if any, or {@code NOT_SET} if not yet set + */ + private int classIndex; + + /** + * Constructs an instance. The output address of this instance is + * initially unknown ({@code -1}) as is the constant pool index. + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + * @param constants {@code non-null;} constants argument + */ + public MultiCstInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers, Constant[] constants) { + super(opcode, position, registers); + + if (constants == null) { + throw new NullPointerException("constants == null"); + } + + this.constants = constants; + this.index = new int[constants.length]; + for (int i = 0; i < this.index.length; ++i) { + if (constants[i] == null) { + throw new NullPointerException("constants[i] == null"); + } + this.index[i] = NOT_SET; + } + this.classIndex = NOT_SET; + } + + private MultiCstInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers, Constant[] constants, int[] index, + int classIndex) { + super(opcode, position, registers); + this.constants = constants; + this.index = index; + this.classIndex = classIndex; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + return new MultiCstInsn(opcode, getPosition(), getRegisters(), + this.constants, this.index, this.classIndex); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new MultiCstInsn(getOpcode(), getPosition(), registers, + this.constants, this.index, this.classIndex); + } + + /** + * Gets the number of constants associated with instruction. + * @return the number of constants. + */ + public int getNumberOfConstants() { + return constants.length; + } + + /** + * Gets a constant associated with the instruction. + * + * @param position the position of the constant to get. + * @return {@code non-null;} the constant argument + */ + public Constant getConstant(int position) { + return constants[position]; + } + + /** + * Gets the DEX index of a constant. It is only valid to call this after + * {@link #setIndex} has been called. + * + * @param position the position of the constant to get the index for. + * @return {@code >= 0;} the constant pool index + */ + public int getIndex(int position) { + if (!hasIndex(position)) { + throw new IllegalStateException("index not yet set for constant " + + position + " value = " + constants[position]); + } + + return index[position]; + } + + /** + * Returns whether the DEX index of a constant has been set. + * + * @param position the position of the constant to test. + * @see #setIndex + * + * @return {@code true} if the index has been set + */ + public boolean hasIndex(int position) { + return (index[position] != NOT_SET); + } + + /** + * Sets the DEX index of a constant. It is only valid to call this method + * once per instance. + * + * @param position the position of the constant to set. + * @param index {@code index >= 0;} the constant pool index + */ + public void setIndex(int position, int index) { + if (index < 0) { + throw new IllegalArgumentException("index < 0"); + } + + if (hasIndex(position)) { + throw new IllegalStateException("index already set"); + } + + this.index[position] = index; + } + + /** + * Gets the class index associated with this instance. The class index + * may be set only once and for one of the constants associated with this + * instance (e.g. a CstMethodRef). It is only valid to + * call this after {@link #setClassIndex} has been called. + * + * @return {@code >= 0;} the constant pool index of the class. + */ + public int getClassIndex() { + if (!hasClassIndex()) { + throw new IllegalStateException("class index not yet set"); + } + + return classIndex; + } + + /** + * Returns whether the class index associated with this instruction has + * been set. + * + * @see #setClassIndex + * + * @return {@code true} if the index has been set, false otherwise + */ + public boolean hasClassIndex() { + return (classIndex != NOT_SET); + } + + /** + * Sets the class index associated with this instruction. This is the + * constant pool index for the class referred to by this instruction. Only + * reference constants have a class, so it is only on instances + * with reference constants that this method should ever be + * called. It is only valid to call this method once per instance. + * + * @param index {@code index >= 0;} the constant pool index of the class + */ + public void setClassIndex(int index) { + if (index < 0) { + throw new IllegalArgumentException("index < 0"); + } + + if (hasClassIndex()) { + throw new IllegalStateException("class index already set"); + } + + this.classIndex = index; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < constants.length; ++i) { + if (sb.length() > 0) sb.append(", "); + sb.append(constants[i].toHuman()); + } + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public String cstString() { + return argString(); + } + + /** {@inheritDoc} */ + @Override + public String cstComment() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < constants.length; ++i) { + if (!hasIndex(i)) { + return ""; + } + + if (i > 0) { + sb.append(", "); + } + sb.append(getConstant(i).typeName()); + sb.append('@'); + + final int currentIndex = getIndex(i); + if (currentIndex < 65536) { + sb.append(Hex.u2(currentIndex)); + } else { + sb.append(Hex.u4(currentIndex)); + } + } + return sb.toString(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/OddSpacer.java b/dexlib/src/main/java/com/android/dx/dex/code/OddSpacer.java new file mode 100644 index 000000000..f44f9ccd9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/OddSpacer.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.io.Opcodes; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.util.AnnotatedOutput; + +/** + * Pseudo-instruction which either turns into a {@code nop} or + * nothingness, in order to make the subsequent instruction have an + * even address. This is used to align (subsequent) instructions that + * require it. + */ +public final class OddSpacer extends VariableSizeInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + */ + public OddSpacer(SourcePosition position) { + super(position, RegisterSpecList.EMPTY); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return (getAddress() & 1); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + if (codeSize() != 0) { + out.writeShort(InsnFormat.codeUnit(Opcodes.NOP, 0)); + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new OddSpacer(getPosition()); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + if (codeSize() == 0) { + return null; + } + + return "nop // spacer"; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/OutputCollector.java b/dexlib/src/main/java/com/android/dx/dex/code/OutputCollector.java new file mode 100644 index 000000000..833c0f21b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/OutputCollector.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.dex.DexOptions; +import java.util.ArrayList; + +/** + * Destination for {@link DalvInsn} instances being output. This class + * receives and collects instructions in two pieces — a primary + * list and a suffix (generally consisting of adjunct data referred to + * by the primary list, such as switch case tables) — which it + * merges and emits back out in the form of a {@link DalvInsnList} + * instance. + */ +public final class OutputCollector { + /** + * {@code non-null;} the associated finisher (which holds the instruction + * list in-progress) + */ + private final OutputFinisher finisher; + + /** + * {@code null-ok;} suffix for the output, or {@code null} if the suffix + * has been appended to the main output (by {@link #appendSuffixToOutput}) + */ + private ArrayList suffix; + + /** + * Constructs an instance. + * + * @param dexOptions {@code non-null;} options for dex output + * @param initialCapacity {@code >= 0;} initial capacity of the output list + * @param suffixInitialCapacity {@code >= 0;} initial capacity of the output + * suffix + * @param regCount {@code >= 0;} register count for the method + * @param paramSize size, in register units, of all the parameters for this method + */ + public OutputCollector(DexOptions dexOptions, int initialCapacity, int suffixInitialCapacity, + int regCount, int paramSize) { + this.finisher = new OutputFinisher(dexOptions, initialCapacity, regCount, paramSize); + this.suffix = new ArrayList(suffixInitialCapacity); + } + + /** + * Adds an instruction to the output. + * + * @param insn {@code non-null;} the instruction to add + */ + public void add(DalvInsn insn) { + finisher.add(insn); + } + + /** + * Reverses a branch which is buried a given number of instructions + * backward in the output. It is illegal to call this unless the + * indicated instruction really is a reversible branch. + * + * @param which how many instructions back to find the branch; + * {@code 0} is the most recently added instruction, + * {@code 1} is the instruction before that, etc. + * @param newTarget {@code non-null;} the new target for the reversed branch + */ + public void reverseBranch(int which, CodeAddress newTarget) { + finisher.reverseBranch(which, newTarget); + } + + /** + * Adds an instruction to the output suffix. + * + * @param insn {@code non-null;} the instruction to add + */ + public void addSuffix(DalvInsn insn) { + suffix.add(insn); + } + + /** + * Gets the results of all the calls on this instance, in the form of + * an {@link OutputFinisher}. + * + * @return {@code non-null;} the output finisher + * @throws UnsupportedOperationException if this method has + * already been called + */ + public OutputFinisher getFinisher() { + if (suffix == null) { + throw new UnsupportedOperationException("already processed"); + } + + appendSuffixToOutput(); + return finisher; + } + + /** + * Helper for {@link #getFinisher}, which appends the suffix to + * the primary output. + */ + private void appendSuffixToOutput() { + int size = suffix.size(); + + for (int i = 0; i < size; i++) { + finisher.add(suffix.get(i)); + } + + suffix = null; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/OutputFinisher.java b/dexlib/src/main/java/com/android/dx/dex/code/OutputFinisher.java new file mode 100644 index 000000000..06616b1f7 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/OutputFinisher.java @@ -0,0 +1,939 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.dex.DexOptions; +import com.android.dx.io.Opcodes; +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.RegisterSpecSet; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstMemberRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.ssa.BasicRegisterMapper; + +import com.android.dex.DexException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; + +/** + * Processor for instruction lists, which takes a "first cut" of + * instruction selection as a basis and produces a "final cut" in the + * form of a {@link DalvInsnList} instance. + */ +public final class OutputFinisher { + /** {@code non-null;} options for dex output */ + private final DexOptions dexOptions; + + /** + * {@code >= 0;} register count for the method, not including any extra + * "reserved" registers needed to translate "difficult" instructions + */ + private final int unreservedRegCount; + + /** {@code non-null;} the list of instructions, per se */ + private ArrayList insns; + + /** whether any instruction has position info */ + private boolean hasAnyPositionInfo; + + /** whether any instruction has local variable info */ + private boolean hasAnyLocalInfo; + + /** + * {@code >= 0;} the count of reserved registers (low-numbered + * registers used when expanding instructions that can't be + * represented simply); becomes valid after a call to {@link + * #massageInstructions} + */ + private int reservedCount; + + /** + * {@code >= 0;} the count of reserved registers just before parameters in order to align them. + */ + private int reservedParameterCount; + + /** + * Size, in register units, of all the parameters to this method + */ + private final int paramSize; + + /** + * Constructs an instance. It initially contains no instructions. + * + * @param dexOptions {@code non-null;} options for dex output + * @param initialCapacity {@code >= 0;} initial capacity of the + * instructions list + * @param regCount {@code >= 0;} register count for the method + * @param paramSize size, in register units, of all the parameters for this method + */ + public OutputFinisher(DexOptions dexOptions, int initialCapacity, int regCount, int paramSize) { + this.dexOptions = dexOptions; + this.unreservedRegCount = regCount; + this.insns = new ArrayList(initialCapacity); + this.reservedCount = -1; + this.hasAnyPositionInfo = false; + this.hasAnyLocalInfo = false; + this.paramSize = paramSize; + } + + /** + * Returns whether any of the instructions added to this instance + * come with position info. + * + * @return whether any of the instructions added to this instance + * come with position info + */ + public boolean hasAnyPositionInfo() { + return hasAnyPositionInfo; + } + + /** + * Returns whether this instance has any local variable information. + * + * @return whether this instance has any local variable information + */ + public boolean hasAnyLocalInfo() { + return hasAnyLocalInfo; + } + + /** + * Helper for {@link #add} which scrutinizes a single + * instruction for local variable information. + * + * @param insn {@code non-null;} instruction to scrutinize + * @return {@code true} iff the instruction refers to any + * named locals + */ + private static boolean hasLocalInfo(DalvInsn insn) { + if (insn instanceof LocalSnapshot) { + RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals(); + int size = specs.size(); + for (int i = 0; i < size; i++) { + if (hasLocalInfo(specs.get(i))) { + return true; + } + } + } else if (insn instanceof LocalStart) { + RegisterSpec spec = ((LocalStart) insn).getLocal(); + if (hasLocalInfo(spec)) { + return true; + } + } + + return false; + } + + /** + * Helper for {@link #hasAnyLocalInfo} which scrutinizes a single + * register spec. + * + * @param spec {@code non-null;} spec to scrutinize + * @return {@code true} iff the spec refers to any + * named locals + */ + private static boolean hasLocalInfo(RegisterSpec spec) { + return (spec != null) + && (spec.getLocalItem().getName() != null); + } + + /** + * Returns the set of all constants referred to by instructions added + * to this instance. + * + * @return {@code non-null;} the set of constants + */ + public HashSet getAllConstants() { + HashSet result = new HashSet(20); + + for (DalvInsn insn : insns) { + addConstants(result, insn); + } + + return result; + } + + /** + * Helper for {@link #getAllConstants} which adds all the info for + * a single instruction. + * + * @param result {@code non-null;} result set to add to + * @param insn {@code non-null;} instruction to scrutinize + */ + private static void addConstants(HashSet result, + DalvInsn insn) { + if (insn instanceof CstInsn) { + Constant cst = ((CstInsn) insn).getConstant(); + result.add(cst); + } else if (insn instanceof MultiCstInsn) { + MultiCstInsn m = (MultiCstInsn) insn; + for (int i = 0; i < m.getNumberOfConstants(); i++) { + result.add(m.getConstant(i)); + } + } else if (insn instanceof LocalSnapshot) { + RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals(); + int size = specs.size(); + for (int i = 0; i < size; i++) { + addConstants(result, specs.get(i)); + } + } else if (insn instanceof LocalStart) { + RegisterSpec spec = ((LocalStart) insn).getLocal(); + addConstants(result, spec); + } + } + + /** + * Helper for {@link #getAllConstants} which adds all the info for + * a single {@code RegisterSpec}. + * + * @param result {@code non-null;} result set to add to + * @param spec {@code null-ok;} register spec to add + */ + private static void addConstants(HashSet result, + RegisterSpec spec) { + if (spec == null) { + return; + } + + LocalItem local = spec.getLocalItem(); + CstString name = local.getName(); + CstString signature = local.getSignature(); + Type type = spec.getType(); + + if (type != Type.KNOWN_NULL) { + result.add(CstType.intern(type)); + } else { + /* If this a "known null", let's use "Object" because that's going to be the + * resulting type in {@link LocalList.MakeState#filterSpec} */ + result.add(CstType.intern(Type.OBJECT)); + } + + if (name != null) { + result.add(name); + } + + if (signature != null) { + result.add(signature); + } + } + + /** + * Adds an instruction to the output. + * + * @param insn {@code non-null;} the instruction to add + */ + public void add(DalvInsn insn) { + insns.add(insn); + updateInfo(insn); + } + + /** + * Inserts an instruction in the output at the given offset. + * + * @param at {@code at >= 0;} what index to insert at + * @param insn {@code non-null;} the instruction to insert + */ + public void insert(int at, DalvInsn insn) { + insns.add(at, insn); + updateInfo(insn); + } + + /** + * Helper for {@link #add} and {@link #insert}, + * which updates the position and local info flags. + * + * @param insn {@code non-null;} an instruction that was just introduced + */ + private void updateInfo(DalvInsn insn) { + if (! hasAnyPositionInfo) { + SourcePosition pos = insn.getPosition(); + if (pos.getLine() >= 0) { + hasAnyPositionInfo = true; + } + } + + if (! hasAnyLocalInfo) { + if (hasLocalInfo(insn)) { + hasAnyLocalInfo = true; + } + } + } + + /** + * Reverses a branch which is buried a given number of instructions + * backward in the output. It is illegal to call this unless the + * indicated instruction really is a reversible branch. + * + * @param which how many instructions back to find the branch; + * {@code 0} is the most recently added instruction, + * {@code 1} is the instruction before that, etc. + * @param newTarget {@code non-null;} the new target for the + * reversed branch + */ + public void reverseBranch(int which, CodeAddress newTarget) { + int size = insns.size(); + int index = size - which - 1; + TargetInsn targetInsn; + + try { + targetInsn = (TargetInsn) insns.get(index); + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("too few instructions"); + } catch (ClassCastException ex) { + // Translate the exception. + throw new IllegalArgumentException("non-reversible instruction"); + } + + /* + * No need to call this.set(), since the format and other info + * are the same. + */ + insns.set(index, targetInsn.withNewTargetAndReversed(newTarget)); + } + + /** + * Assigns indices in all instructions that need them, using the + * given callback to perform lookups. This should be called before + * calling {@link #finishProcessingAndGetList}. + * + * @param callback {@code non-null;} callback object + */ + public void assignIndices(DalvCode.AssignIndicesCallback callback) { + for (DalvInsn insn : insns) { + if (insn instanceof CstInsn) { + assignIndices((CstInsn) insn, callback); + } else if (insn instanceof MultiCstInsn) { + assignIndices((MultiCstInsn) insn, callback); + } + } + } + + /** + * Helper for {@link #assignIndices} which does assignment for one + * instruction. + * + * @param insn {@code non-null;} the instruction + * @param callback {@code non-null;} the callback + */ + private static void assignIndices(CstInsn insn, + DalvCode.AssignIndicesCallback callback) { + Constant cst = insn.getConstant(); + int index = callback.getIndex(cst); + + if (index >= 0) { + insn.setIndex(index); + } + + if (cst instanceof CstMemberRef) { + CstMemberRef member = (CstMemberRef) cst; + CstType definer = member.getDefiningClass(); + index = callback.getIndex(definer); + // TODO(oth): what scenarios is this guard valid under? Is it not just an error? + if (index >= 0) { + insn.setClassIndex(index); + } + } + } + + /** + * Helper for {@link #assignIndices} which does assignment for one + * instruction. + * + * @param insn {@code non-null;} the instruction + * @param callback {@code non-null;} the callback + */ + private static void assignIndices(MultiCstInsn insn, DalvCode.AssignIndicesCallback callback) { + for (int i = 0; i < insn.getNumberOfConstants(); ++i) { + Constant cst = insn.getConstant(i); + int index = callback.getIndex(cst); + insn.setIndex(i, index); + + if (cst instanceof CstMemberRef) { + CstMemberRef member = (CstMemberRef) cst; + CstType definer = member.getDefiningClass(); + index = callback.getIndex(definer); + insn.setClassIndex(index); + } + } + } + + /** + * Does final processing on this instance and gets the output as + * a {@link DalvInsnList}. Final processing consists of: + * + *
    + *
  • optionally renumbering registers (to make room as needed for + * expanded instructions)
  • + *
  • picking a final opcode for each instruction
  • + *
  • rewriting instructions, because of register number, + * constant pool index, or branch target size issues
  • + *
  • assigning final addresses
  • + *
+ * + *

Note: This method may only be called once per instance + * of this class.

+ * + * @return {@code non-null;} the output list + * @throws UnsupportedOperationException if this method has + * already been called + */ + public DalvInsnList finishProcessingAndGetList() { + if (reservedCount >= 0) { + throw new UnsupportedOperationException("already processed"); + } + + Dop[] opcodes = makeOpcodesArray(); + reserveRegisters(opcodes); + if (dexOptions.ALIGN_64BIT_REGS_IN_OUTPUT_FINISHER) { + align64bits(opcodes); + } + massageInstructions(opcodes); + assignAddressesAndFixBranches(); + + return DalvInsnList.makeImmutable(insns, reservedCount + unreservedRegCount + + reservedParameterCount); + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which extracts + * the opcode out of each instruction into a separate array, to be + * further manipulated as things progress. + * + * @return {@code non-null;} the array of opcodes + */ + private Dop[] makeOpcodesArray() { + int size = insns.size(); + Dop[] result = new Dop[size]; + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + result[i] = insn.getOpcode(); + } + + return result; + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which figures + * out how many reserved registers are required and then reserving + * them. It also updates the given {@code opcodes} array so + * as to avoid extra work when constructing the massaged + * instruction list. + * + * @param opcodes {@code non-null;} array of per-instruction + * opcode selections + * @return true if reservedCount is expanded, false otherwise + */ + private boolean reserveRegisters(Dop[] opcodes) { + boolean reservedCountExpanded = false; + int oldReservedCount = (reservedCount < 0) ? 0 : reservedCount; + + /* + * Call calculateReservedCount() and then perform register + * reservation, repeatedly until no new reservations happen. + */ + for (;;) { + int newReservedCount = calculateReservedCount(opcodes); + if (oldReservedCount >= newReservedCount) { + break; + } + + reservedCountExpanded = true; + + int reservedDifference = newReservedCount - oldReservedCount; + int size = insns.size(); + + for (int i = 0; i < size; i++) { + /* + * CodeAddress instance identity is used to link + * TargetInsns to their targets, so it is + * inappropriate to make replacements, and they don't + * have registers in any case. Hence, the instanceof + * test below. + */ + DalvInsn insn = insns.get(i); + if (!(insn instanceof CodeAddress)) { + /* + * No need to call this.set() since the format and + * other info are the same. + */ + insns.set(i, insn.withRegisterOffset(reservedDifference)); + } + } + + oldReservedCount = newReservedCount; + } + + reservedCount = oldReservedCount; + + return reservedCountExpanded; + } + + /** + * Helper for {@link #reserveRegisters}, which does one + * pass over the instructions, calculating the number of + * registers that need to be reserved. It also updates the + * {@code opcodes} list to help avoid extra work in future + * register reservation passes. + * + * @param opcodes {@code non-null;} array of per-instruction + * opcode selections + * @return {@code >= 0;} the count of reserved registers + */ + private int calculateReservedCount(Dop[] opcodes) { + int size = insns.size(); + + /* + * Potential new value of reservedCount, which gets updated in the + * following loop. It starts out with the existing reservedCount + * and gets increased if it turns out that additional registers + * need to be reserved. + */ + int newReservedCount = reservedCount; + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + Dop originalOpcode = opcodes[i]; + Dop newOpcode = findOpcodeForInsn(insn, originalOpcode); + + if (newOpcode == null) { + /* + * The instruction will need to be expanded, so find the + * expanded opcode and reserve registers for it. + */ + Dop expandedOp = findExpandedOpcodeForInsn(insn); + BitSet compatRegs = expandedOp.getFormat().compatibleRegs(insn); + int reserve = insn.getMinimumRegisterRequirement(compatRegs); + if (reserve > newReservedCount) { + newReservedCount = reserve; + } + } else if (originalOpcode == newOpcode) { + continue; + } + + opcodes[i] = newOpcode; + } + + return newReservedCount; + } + + /** + * Attempts to fit the given instruction into a specific opcode, + * returning the opcode whose format that the instruction fits + * into or {@code null} to indicate that the instruction will need + * to be expanded. This fitting process starts with the given + * opcode as a first "best guess" and then pessimizes from there + * if necessary. + * + * @param insn {@code non-null;} the instruction in question + * @param guess {@code null-ok;} the current guess as to the best + * opcode; {@code null} means that no simple opcode fits + * @return {@code null-ok;} a possibly-different opcode; either a + * {@code non-null} good fit or {@code null} to indicate that no + * simple opcode fits + */ + private Dop findOpcodeForInsn(DalvInsn insn, Dop guess) { + /* + * Note: The initial guess might be null, meaning that an + * earlier call to this method already determined that there + * was no possible simple opcode fit. + */ + + while (guess != null) { + if (guess.getFormat().isCompatible(insn)) { + /* + * Don't break out for const_string to generate jumbo version + * when option is enabled. + */ + if (!dexOptions.forceJumbo || + guess.getOpcode() != Opcodes.CONST_STRING) { + break; + } + } + + guess = Dops.getNextOrNull(guess, dexOptions); + } + + return guess; + } + + /** + * Finds the proper opcode for the given instruction, ignoring + * register constraints. + * + * @param insn {@code non-null;} the instruction in question + * @return {@code non-null;} the opcode that fits + */ + private Dop findExpandedOpcodeForInsn(DalvInsn insn) { + Dop result = findOpcodeForInsn(insn.getLowRegVersion(), insn.getOpcode()); + if (result == null) { + throw new DexException("No expanded opcode for " + insn); + } + return result; + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which goes + * through each instruction in the output, making sure its opcode + * can accomodate its arguments. In cases where the opcode is + * unable to do so, this replaces the instruction with a larger + * instruction with identical semantics that will work. + * + *

This method may also reserve a number of low-numbered + * registers, renumbering the instructions' original registers, in + * order to have register space available in which to move + * very-high registers when expanding instructions into + * multi-instruction sequences. This expansion is done when no + * simple instruction format can be found for a given instruction that + * is able to accomodate that instruction's registers.

+ * + *

This method ignores issues of branch target size, since + * final addresses aren't known at the point that this method is + * called.

+ * + * @param opcodes {@code non-null;} array of per-instruction + * opcode selections + */ + private void massageInstructions(Dop[] opcodes) { + if (reservedCount == 0) { + /* + * The easy common case: No registers were reserved, so we + * merely need to replace any instructions whose format + * (and hence whose opcode) changed during the reservation + * pass, but all instructions will stay at their original + * indices, and the instruction list doesn't grow. + */ + int size = insns.size(); + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + Dop originalOpcode = insn.getOpcode(); + Dop currentOpcode = opcodes[i]; + + if (originalOpcode != currentOpcode) { + insns.set(i, insn.withOpcode(currentOpcode)); + } + } + } else { + /* + * The difficult uncommon case: Some instructions have to be + * expanded to deal with high registers. + */ + insns = performExpansion(opcodes); + } + } + + /** + * Helper for {@link #massageInstructions}, which constructs a + * replacement list, where each {link DalvInsn} instance that + * couldn't be represented simply (due to register representation + * problems) is expanded into a series of instances that together + * perform the proper function. + * + * @param opcodes {@code non-null;} array of per-instruction + * opcode selections + * @return {@code non-null;} the replacement list + */ + private ArrayList performExpansion(Dop[] opcodes) { + int size = insns.size(); + ArrayList result = new ArrayList(size * 2); + + ArrayList closelyBoundAddresses = new ArrayList(); + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + Dop originalOpcode = insn.getOpcode(); + Dop currentOpcode = opcodes[i]; + DalvInsn prefix; + DalvInsn suffix; + + if (currentOpcode != null) { + // No expansion is necessary. + prefix = null; + suffix = null; + } else { + // Expansion is required. + currentOpcode = findExpandedOpcodeForInsn(insn); + BitSet compatRegs = + currentOpcode.getFormat().compatibleRegs(insn); + prefix = insn.expandedPrefix(compatRegs); + suffix = insn.expandedSuffix(compatRegs); + + // Expand necessary registers to fit the new format + insn = insn.expandedVersion(compatRegs); + } + + if (insn instanceof CodeAddress) { + // If we have a closely bound address, don't add it yet, + // because we need to add it after the prefix for the + // instruction it is bound to. + if (((CodeAddress) insn).getBindsClosely()) { + closelyBoundAddresses.add((CodeAddress)insn); + continue; + } + } + + if (prefix != null) { + result.add(prefix); + } + + // Add any pending closely bound addresses + if (!(insn instanceof ZeroSizeInsn) && closelyBoundAddresses.size() > 0) { + for (CodeAddress codeAddress: closelyBoundAddresses) { + result.add(codeAddress); + } + closelyBoundAddresses.clear(); + } + + if (currentOpcode != originalOpcode) { + insn = insn.withOpcode(currentOpcode); + } + result.add(insn); + + if (suffix != null) { + result.add(suffix); + } + } + + return result; + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which assigns + * addresses to each instruction, possibly rewriting branches to + * fix ones that wouldn't otherwise be able to reach their + * targets. + */ + private void assignAddressesAndFixBranches() { + for (;;) { + assignAddresses(); + if (!fixBranches()) { + break; + } + } + } + + /** + * Helper for {@link #assignAddressesAndFixBranches}, which + * assigns an address to each instruction, in order. + */ + private void assignAddresses() { + int address = 0; + int size = insns.size(); + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + insn.setAddress(address); + address += insn.codeSize(); + } + } + + /** + * Helper for {@link #assignAddressesAndFixBranches}, which checks + * the branch target size requirement of each branch instruction + * to make sure it fits. For instructions that don't fit, this + * rewrites them to use a {@code goto} of some sort. In the + * case of a conditional branch that doesn't fit, the sense of the + * test is reversed in order to branch around a {@code goto} + * to the original target. + * + * @return whether any branches had to be fixed + */ + private boolean fixBranches() { + int size = insns.size(); + boolean anyFixed = false; + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + if (!(insn instanceof TargetInsn)) { + // This loop only needs to inspect TargetInsns. + continue; + } + + Dop opcode = insn.getOpcode(); + TargetInsn target = (TargetInsn) insn; + + if (opcode.getFormat().branchFits(target)) { + continue; + } + + if (opcode.getFamily() == Opcodes.GOTO) { + // It is a goto; widen it if possible. + opcode = findOpcodeForInsn(insn, opcode); + if (opcode == null) { + /* + * The branch is already maximally large. This should + * only be possible if a method somehow manages to have + * more than 2^31 code units. + */ + throw new UnsupportedOperationException("method too long"); + } + insns.set(i, insn.withOpcode(opcode)); + } else { + /* + * It is a conditional: Reverse its sense, and arrange for + * it to branch around an absolute goto to the original + * branch target. + * + * Note: An invariant of the list being processed is + * that every TargetInsn is followed by a CodeAddress. + * Hence, it is always safe to get the next element + * after a TargetInsn and cast it to CodeAddress, as + * is happening a few lines down. + * + * Also note: Size gets incremented by one here, as we + * have -- in the net -- added one additional element + * to the list, so we increment i to match. The added + * and changed elements will be inspected by a repeat + * call to this method after this invocation returns. + */ + CodeAddress newTarget; + try { + newTarget = (CodeAddress) insns.get(i + 1); + } catch (IndexOutOfBoundsException ex) { + // The TargetInsn / CodeAddress invariant was violated. + throw new IllegalStateException( + "unpaired TargetInsn (dangling)"); + } catch (ClassCastException ex) { + // The TargetInsn / CodeAddress invariant was violated. + throw new IllegalStateException("unpaired TargetInsn"); + } + TargetInsn gotoInsn = + new TargetInsn(Dops.GOTO, target.getPosition(), + RegisterSpecList.EMPTY, target.getTarget()); + insns.set(i, gotoInsn); + insns.add(i, target.withNewTargetAndReversed(newTarget)); + size++; + i++; + } + + anyFixed = true; + } + + return anyFixed; + } + + private void align64bits(Dop[] opcodes) { + while (true) { + int notAligned64bitRegAccess = 0; + int aligned64bitRegAccess = 0; + int notAligned64bitParamAccess = 0; + int aligned64bitParamAccess = 0; + int lastParameter = unreservedRegCount + reservedCount + reservedParameterCount; + int firstParameter = lastParameter - paramSize; + + // Collects the number of time that 64-bit registers are accessed aligned or not. + for (DalvInsn insn : insns) { + RegisterSpecList regs = insn.getRegisters(); + for (int usedRegIdx = 0; usedRegIdx < regs.size(); usedRegIdx++) { + RegisterSpec reg = regs.get(usedRegIdx); + if (reg.isCategory2()) { + boolean isParameter = reg.getReg() >= firstParameter; + if (reg.isEvenRegister()) { + if (isParameter) { + aligned64bitParamAccess++; + } else { + aligned64bitRegAccess++; + } + } else { + if (isParameter) { + notAligned64bitParamAccess++; + } else { + notAligned64bitRegAccess++; + } + } + } + } + } + + if (notAligned64bitParamAccess > aligned64bitParamAccess + && notAligned64bitRegAccess > aligned64bitRegAccess) { + addReservedRegisters(1); + } else if (notAligned64bitParamAccess > aligned64bitParamAccess) { + addReservedParameters(1); + } else if (notAligned64bitRegAccess > aligned64bitRegAccess) { + addReservedRegisters(1); + + // Need to shift parameters if they exist and if number of unaligned is greater than + // aligned. We test the opposite because we previously shift all registers by one, + // so the number of aligned become the number of unaligned. + if (paramSize != 0 && aligned64bitParamAccess > notAligned64bitParamAccess) { + addReservedParameters(1); + } + } else { + break; + } + + if (!reserveRegisters(opcodes)) { + break; + } + } + } + + private void addReservedParameters(int delta) { + shiftParameters(delta); + reservedParameterCount += delta; + } + + private void addReservedRegisters(int delta) { + shiftAllRegisters(delta); + reservedCount += delta; + } + + private void shiftAllRegisters(int delta) { + int insnSize = insns.size(); + + for (int i = 0; i < insnSize; i++) { + DalvInsn insn = insns.get(i); + // Since there is no need to replace CodeAddress since it does not use registers, skips it to + // avoid to update all TargetInsn that contain a reference to CodeAddress + if (!(insn instanceof CodeAddress)) { + insns.set(i, insn.withRegisterOffset(delta)); + } + } + } + + private void shiftParameters(int delta) { + int insnSize = insns.size(); + int lastParameter = unreservedRegCount + reservedCount + reservedParameterCount; + int firstParameter = lastParameter - paramSize; + + BasicRegisterMapper mapper = new BasicRegisterMapper(lastParameter); + for (int i = 0; i < lastParameter; i++) { + if (i >= firstParameter) { + mapper.addMapping(i, i + delta, 1); + } else { + mapper.addMapping(i, i, 1); + } + } + + for (int i = 0; i < insnSize; i++) { + DalvInsn insn = insns.get(i); + // Since there is no need to replace CodeAddress since it does not use registers, skips it to + // avoid to update all TargetInsn that contain a reference to CodeAddress + if (!(insn instanceof CodeAddress)) { + insns.set(i, insn.withMapper(mapper)); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/PositionList.java b/dexlib/src/main/java/com/android/dx/dex/code/PositionList.java new file mode 100644 index 000000000..1e07e4669 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/PositionList.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.util.FixedSizeList; + +/** + * List of source position entries. This class includes a utility + * method to extract an instance out of a {@link DalvInsnList}. + */ +public final class PositionList extends FixedSizeList { + /** {@code non-null;} empty instance */ + public static final PositionList EMPTY = new PositionList(0); + + /** + * constant for {@link #make} to indicate that no actual position + * information should be returned + */ + public static final int NONE = 1; + + /** + * constant for {@link #make} to indicate that only line number + * transitions should be returned + */ + public static final int LINES = 2; + + /** + * constant for {@link #make} to indicate that only "important" position + * information should be returned. This includes block starts and + * instructions that might throw. + */ + public static final int IMPORTANT = 3; + + /** + * Extracts and returns the source position information out of an + * instruction list. + * + * @param insns {@code non-null;} instructions to convert + * @param howMuch how much information should be included; one of the + * static constants defined by this class + * @return {@code non-null;} the positions list + */ + public static PositionList make(DalvInsnList insns, int howMuch) { + switch (howMuch) { + case NONE: { + return EMPTY; + } + case LINES: + case IMPORTANT: { + // Valid. + break; + } + default: { + throw new IllegalArgumentException("bogus howMuch"); + } + } + + SourcePosition noInfo = SourcePosition.NO_INFO; + SourcePosition cur = noInfo; + int sz = insns.size(); + PositionList.Entry[] arr = new PositionList.Entry[sz]; + boolean lastWasTarget = false; + int at = 0; + + for (int i = 0; i < sz; i++) { + DalvInsn insn = insns.get(i); + + if (insn instanceof CodeAddress) { + lastWasTarget = true;; + continue; + } + + SourcePosition pos = insn.getPosition(); + + if (pos.equals(noInfo) || pos.sameLine(cur)) { + continue; + } + + if ((howMuch == IMPORTANT) && !lastWasTarget) { + continue; + } + + cur = pos; + arr[at] = new PositionList.Entry(insn.getAddress(), pos); + at++; + + lastWasTarget = false; + } + + PositionList result = new PositionList(at); + for (int i = 0; i < at; i++) { + result.set(i, arr[i]); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the list + */ + public PositionList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** + * Entry in a position list. + */ + public static class Entry { + /** {@code >= 0;} address of this entry */ + private final int address; + + /** {@code non-null;} corresponding source position information */ + private final SourcePosition position; + + /** + * Constructs an instance. + * + * @param address {@code >= 0;} address of this entry + * @param position {@code non-null;} corresponding source position information + */ + public Entry (int address, SourcePosition position) { + if (address < 0) { + throw new IllegalArgumentException("address < 0"); + } + + if (position == null) { + throw new NullPointerException("position == null"); + } + + this.address = address; + this.position = position; + } + + /** + * Gets the address. + * + * @return {@code >= 0;} the address + */ + public int getAddress() { + return address; + } + + /** + * Gets the source position information. + * + * @return {@code non-null;} the position information + */ + public SourcePosition getPosition() { + return position; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/RopToDop.java b/dexlib/src/main/java/com/android/dx/dex/code/RopToDop.java new file mode 100644 index 000000000..65c5fab0f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/RopToDop.java @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.ThrowingCstInsn; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import java.util.HashMap; + +/** + * Translator from rop-level {@link Insn} instances to corresponding + * {@link Dop} instances. + */ +public final class RopToDop { + /** {@code non-null;} map from all the common rops to dalvik opcodes */ + private static final HashMap MAP; + + /** + * This class is uninstantiable. + */ + private RopToDop() { + // This space intentionally left blank. + } + + /* + * The following comment lists each opcode that should be considered + * the "head" of an opcode chain, in terms of the process of fitting + * an instruction's arguments to an actual opcode. This list is + * automatically generated and may be of use in double-checking the + * manually-generated static initialization code for this class. + * + * TODO: Make opcode-gen produce useful code in this case instead + * of just a comment. + */ + + // BEGIN(first-opcodes); GENERATED AUTOMATICALLY BY opcode-gen + // Opcodes.NOP + // Opcodes.MOVE + // Opcodes.MOVE_WIDE + // Opcodes.MOVE_OBJECT + // Opcodes.MOVE_RESULT + // Opcodes.MOVE_RESULT_WIDE + // Opcodes.MOVE_RESULT_OBJECT + // Opcodes.MOVE_EXCEPTION + // Opcodes.RETURN_VOID + // Opcodes.RETURN + // Opcodes.RETURN_WIDE + // Opcodes.RETURN_OBJECT + // Opcodes.CONST_4 + // Opcodes.CONST_WIDE_16 + // Opcodes.CONST_STRING + // Opcodes.CONST_CLASS + // Opcodes.MONITOR_ENTER + // Opcodes.MONITOR_EXIT + // Opcodes.CHECK_CAST + // Opcodes.INSTANCE_OF + // Opcodes.ARRAY_LENGTH + // Opcodes.NEW_INSTANCE + // Opcodes.NEW_ARRAY + // Opcodes.FILLED_NEW_ARRAY + // Opcodes.FILL_ARRAY_DATA + // Opcodes.THROW + // Opcodes.GOTO + // Opcodes.PACKED_SWITCH + // Opcodes.SPARSE_SWITCH + // Opcodes.CMPL_FLOAT + // Opcodes.CMPG_FLOAT + // Opcodes.CMPL_DOUBLE + // Opcodes.CMPG_DOUBLE + // Opcodes.CMP_LONG + // Opcodes.IF_EQ + // Opcodes.IF_NE + // Opcodes.IF_LT + // Opcodes.IF_GE + // Opcodes.IF_GT + // Opcodes.IF_LE + // Opcodes.IF_EQZ + // Opcodes.IF_NEZ + // Opcodes.IF_LTZ + // Opcodes.IF_GEZ + // Opcodes.IF_GTZ + // Opcodes.IF_LEZ + // Opcodes.AGET + // Opcodes.AGET_WIDE + // Opcodes.AGET_OBJECT + // Opcodes.AGET_BOOLEAN + // Opcodes.AGET_BYTE + // Opcodes.AGET_CHAR + // Opcodes.AGET_SHORT + // Opcodes.APUT + // Opcodes.APUT_WIDE + // Opcodes.APUT_OBJECT + // Opcodes.APUT_BOOLEAN + // Opcodes.APUT_BYTE + // Opcodes.APUT_CHAR + // Opcodes.APUT_SHORT + // Opcodes.IGET + // Opcodes.IGET_WIDE + // Opcodes.IGET_OBJECT + // Opcodes.IGET_BOOLEAN + // Opcodes.IGET_BYTE + // Opcodes.IGET_CHAR + // Opcodes.IGET_SHORT + // Opcodes.IPUT + // Opcodes.IPUT_WIDE + // Opcodes.IPUT_OBJECT + // Opcodes.IPUT_BOOLEAN + // Opcodes.IPUT_BYTE + // Opcodes.IPUT_CHAR + // Opcodes.IPUT_SHORT + // Opcodes.SGET + // Opcodes.SGET_WIDE + // Opcodes.SGET_OBJECT + // Opcodes.SGET_BOOLEAN + // Opcodes.SGET_BYTE + // Opcodes.SGET_CHAR + // Opcodes.SGET_SHORT + // Opcodes.SPUT + // Opcodes.SPUT_WIDE + // Opcodes.SPUT_OBJECT + // Opcodes.SPUT_BOOLEAN + // Opcodes.SPUT_BYTE + // Opcodes.SPUT_CHAR + // Opcodes.SPUT_SHORT + // Opcodes.INVOKE_VIRTUAL + // Opcodes.INVOKE_SUPER + // Opcodes.INVOKE_DIRECT + // Opcodes.INVOKE_STATIC + // Opcodes.INVOKE_INTERFACE + // Opcodes.NEG_INT + // Opcodes.NOT_INT + // Opcodes.NEG_LONG + // Opcodes.NOT_LONG + // Opcodes.NEG_FLOAT + // Opcodes.NEG_DOUBLE + // Opcodes.INT_TO_LONG + // Opcodes.INT_TO_FLOAT + // Opcodes.INT_TO_DOUBLE + // Opcodes.LONG_TO_INT + // Opcodes.LONG_TO_FLOAT + // Opcodes.LONG_TO_DOUBLE + // Opcodes.FLOAT_TO_INT + // Opcodes.FLOAT_TO_LONG + // Opcodes.FLOAT_TO_DOUBLE + // Opcodes.DOUBLE_TO_INT + // Opcodes.DOUBLE_TO_LONG + // Opcodes.DOUBLE_TO_FLOAT + // Opcodes.INT_TO_BYTE + // Opcodes.INT_TO_CHAR + // Opcodes.INT_TO_SHORT + // Opcodes.ADD_INT_2ADDR + // Opcodes.SUB_INT_2ADDR + // Opcodes.MUL_INT_2ADDR + // Opcodes.DIV_INT_2ADDR + // Opcodes.REM_INT_2ADDR + // Opcodes.AND_INT_2ADDR + // Opcodes.OR_INT_2ADDR + // Opcodes.XOR_INT_2ADDR + // Opcodes.SHL_INT_2ADDR + // Opcodes.SHR_INT_2ADDR + // Opcodes.USHR_INT_2ADDR + // Opcodes.ADD_LONG_2ADDR + // Opcodes.SUB_LONG_2ADDR + // Opcodes.MUL_LONG_2ADDR + // Opcodes.DIV_LONG_2ADDR + // Opcodes.REM_LONG_2ADDR + // Opcodes.AND_LONG_2ADDR + // Opcodes.OR_LONG_2ADDR + // Opcodes.XOR_LONG_2ADDR + // Opcodes.SHL_LONG_2ADDR + // Opcodes.SHR_LONG_2ADDR + // Opcodes.USHR_LONG_2ADDR + // Opcodes.ADD_FLOAT_2ADDR + // Opcodes.SUB_FLOAT_2ADDR + // Opcodes.MUL_FLOAT_2ADDR + // Opcodes.DIV_FLOAT_2ADDR + // Opcodes.REM_FLOAT_2ADDR + // Opcodes.ADD_DOUBLE_2ADDR + // Opcodes.SUB_DOUBLE_2ADDR + // Opcodes.MUL_DOUBLE_2ADDR + // Opcodes.DIV_DOUBLE_2ADDR + // Opcodes.REM_DOUBLE_2ADDR + // Opcodes.ADD_INT_LIT8 + // Opcodes.RSUB_INT_LIT8 + // Opcodes.MUL_INT_LIT8 + // Opcodes.DIV_INT_LIT8 + // Opcodes.REM_INT_LIT8 + // Opcodes.AND_INT_LIT8 + // Opcodes.OR_INT_LIT8 + // Opcodes.XOR_INT_LIT8 + // Opcodes.SHL_INT_LIT8 + // Opcodes.SHR_INT_LIT8 + // Opcodes.USHR_INT_LIT8 + // Opcodes.INVOKE_POLYMORPHIC + // Opcodes.INVOKE_CUSTOM + // END(first-opcodes) + + static { + /* + * Note: The choices made here are to pick the optimistically + * smallest Dalvik opcode, and leave it to later processing to + * pessimize. See the automatically-generated comment above + * for reference. + */ + MAP = new HashMap(400); + MAP.put(Rops.NOP, Dops.NOP); + MAP.put(Rops.MOVE_INT, Dops.MOVE); + MAP.put(Rops.MOVE_LONG, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_FLOAT, Dops.MOVE); + MAP.put(Rops.MOVE_DOUBLE, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_OBJECT, Dops.MOVE_OBJECT); + MAP.put(Rops.MOVE_PARAM_INT, Dops.MOVE); + MAP.put(Rops.MOVE_PARAM_LONG, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_PARAM_FLOAT, Dops.MOVE); + MAP.put(Rops.MOVE_PARAM_DOUBLE, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_PARAM_OBJECT, Dops.MOVE_OBJECT); + + /* + * Note: No entry for MOVE_EXCEPTION, since it varies by + * exception type. (That is, there is no unique instance to + * add to the map.) + */ + + MAP.put(Rops.CONST_INT, Dops.CONST_4); + MAP.put(Rops.CONST_LONG, Dops.CONST_WIDE_16); + MAP.put(Rops.CONST_FLOAT, Dops.CONST_4); + MAP.put(Rops.CONST_DOUBLE, Dops.CONST_WIDE_16); + + /* + * Note: No entry for CONST_OBJECT, since it needs to turn + * into either CONST_STRING or CONST_CLASS. + */ + + /* + * TODO: I think the only case of this is for null, and + * const/4 should cover that. + */ + MAP.put(Rops.CONST_OBJECT_NOTHROW, Dops.CONST_4); + + MAP.put(Rops.GOTO, Dops.GOTO); + MAP.put(Rops.IF_EQZ_INT, Dops.IF_EQZ); + MAP.put(Rops.IF_NEZ_INT, Dops.IF_NEZ); + MAP.put(Rops.IF_LTZ_INT, Dops.IF_LTZ); + MAP.put(Rops.IF_GEZ_INT, Dops.IF_GEZ); + MAP.put(Rops.IF_LEZ_INT, Dops.IF_LEZ); + MAP.put(Rops.IF_GTZ_INT, Dops.IF_GTZ); + MAP.put(Rops.IF_EQZ_OBJECT, Dops.IF_EQZ); + MAP.put(Rops.IF_NEZ_OBJECT, Dops.IF_NEZ); + MAP.put(Rops.IF_EQ_INT, Dops.IF_EQ); + MAP.put(Rops.IF_NE_INT, Dops.IF_NE); + MAP.put(Rops.IF_LT_INT, Dops.IF_LT); + MAP.put(Rops.IF_GE_INT, Dops.IF_GE); + MAP.put(Rops.IF_LE_INT, Dops.IF_LE); + MAP.put(Rops.IF_GT_INT, Dops.IF_GT); + MAP.put(Rops.IF_EQ_OBJECT, Dops.IF_EQ); + MAP.put(Rops.IF_NE_OBJECT, Dops.IF_NE); + MAP.put(Rops.SWITCH, Dops.SPARSE_SWITCH); + MAP.put(Rops.ADD_INT, Dops.ADD_INT_2ADDR); + MAP.put(Rops.ADD_LONG, Dops.ADD_LONG_2ADDR); + MAP.put(Rops.ADD_FLOAT, Dops.ADD_FLOAT_2ADDR); + MAP.put(Rops.ADD_DOUBLE, Dops.ADD_DOUBLE_2ADDR); + MAP.put(Rops.SUB_INT, Dops.SUB_INT_2ADDR); + MAP.put(Rops.SUB_LONG, Dops.SUB_LONG_2ADDR); + MAP.put(Rops.SUB_FLOAT, Dops.SUB_FLOAT_2ADDR); + MAP.put(Rops.SUB_DOUBLE, Dops.SUB_DOUBLE_2ADDR); + MAP.put(Rops.MUL_INT, Dops.MUL_INT_2ADDR); + MAP.put(Rops.MUL_LONG, Dops.MUL_LONG_2ADDR); + MAP.put(Rops.MUL_FLOAT, Dops.MUL_FLOAT_2ADDR); + MAP.put(Rops.MUL_DOUBLE, Dops.MUL_DOUBLE_2ADDR); + MAP.put(Rops.DIV_INT, Dops.DIV_INT_2ADDR); + MAP.put(Rops.DIV_LONG, Dops.DIV_LONG_2ADDR); + MAP.put(Rops.DIV_FLOAT, Dops.DIV_FLOAT_2ADDR); + MAP.put(Rops.DIV_DOUBLE, Dops.DIV_DOUBLE_2ADDR); + MAP.put(Rops.REM_INT, Dops.REM_INT_2ADDR); + MAP.put(Rops.REM_LONG, Dops.REM_LONG_2ADDR); + MAP.put(Rops.REM_FLOAT, Dops.REM_FLOAT_2ADDR); + MAP.put(Rops.REM_DOUBLE, Dops.REM_DOUBLE_2ADDR); + MAP.put(Rops.NEG_INT, Dops.NEG_INT); + MAP.put(Rops.NEG_LONG, Dops.NEG_LONG); + MAP.put(Rops.NEG_FLOAT, Dops.NEG_FLOAT); + MAP.put(Rops.NEG_DOUBLE, Dops.NEG_DOUBLE); + MAP.put(Rops.AND_INT, Dops.AND_INT_2ADDR); + MAP.put(Rops.AND_LONG, Dops.AND_LONG_2ADDR); + MAP.put(Rops.OR_INT, Dops.OR_INT_2ADDR); + MAP.put(Rops.OR_LONG, Dops.OR_LONG_2ADDR); + MAP.put(Rops.XOR_INT, Dops.XOR_INT_2ADDR); + MAP.put(Rops.XOR_LONG, Dops.XOR_LONG_2ADDR); + MAP.put(Rops.SHL_INT, Dops.SHL_INT_2ADDR); + MAP.put(Rops.SHL_LONG, Dops.SHL_LONG_2ADDR); + MAP.put(Rops.SHR_INT, Dops.SHR_INT_2ADDR); + MAP.put(Rops.SHR_LONG, Dops.SHR_LONG_2ADDR); + MAP.put(Rops.USHR_INT, Dops.USHR_INT_2ADDR); + MAP.put(Rops.USHR_LONG, Dops.USHR_LONG_2ADDR); + MAP.put(Rops.NOT_INT, Dops.NOT_INT); + MAP.put(Rops.NOT_LONG, Dops.NOT_LONG); + + MAP.put(Rops.ADD_CONST_INT, Dops.ADD_INT_LIT8); + // Note: No dalvik ops for other types of add_const. + + MAP.put(Rops.SUB_CONST_INT, Dops.RSUB_INT_LIT8); + /* + * Note: No dalvik ops for any type of sub_const; instead + * there's a *reverse* sub (constant - reg) for ints only. + */ + + MAP.put(Rops.MUL_CONST_INT, Dops.MUL_INT_LIT8); + // Note: No dalvik ops for other types of mul_const. + + MAP.put(Rops.DIV_CONST_INT, Dops.DIV_INT_LIT8); + // Note: No dalvik ops for other types of div_const. + + MAP.put(Rops.REM_CONST_INT, Dops.REM_INT_LIT8); + // Note: No dalvik ops for other types of rem_const. + + MAP.put(Rops.AND_CONST_INT, Dops.AND_INT_LIT8); + // Note: No dalvik op for and_const_long. + + MAP.put(Rops.OR_CONST_INT, Dops.OR_INT_LIT8); + // Note: No dalvik op for or_const_long. + + MAP.put(Rops.XOR_CONST_INT, Dops.XOR_INT_LIT8); + // Note: No dalvik op for xor_const_long. + + MAP.put(Rops.SHL_CONST_INT, Dops.SHL_INT_LIT8); + // Note: No dalvik op for shl_const_long. + + MAP.put(Rops.SHR_CONST_INT, Dops.SHR_INT_LIT8); + // Note: No dalvik op for shr_const_long. + + MAP.put(Rops.USHR_CONST_INT, Dops.USHR_INT_LIT8); + // Note: No dalvik op for shr_const_long. + + MAP.put(Rops.CMPL_LONG, Dops.CMP_LONG); + MAP.put(Rops.CMPL_FLOAT, Dops.CMPL_FLOAT); + MAP.put(Rops.CMPL_DOUBLE, Dops.CMPL_DOUBLE); + MAP.put(Rops.CMPG_FLOAT, Dops.CMPG_FLOAT); + MAP.put(Rops.CMPG_DOUBLE, Dops.CMPG_DOUBLE); + MAP.put(Rops.CONV_L2I, Dops.LONG_TO_INT); + MAP.put(Rops.CONV_F2I, Dops.FLOAT_TO_INT); + MAP.put(Rops.CONV_D2I, Dops.DOUBLE_TO_INT); + MAP.put(Rops.CONV_I2L, Dops.INT_TO_LONG); + MAP.put(Rops.CONV_F2L, Dops.FLOAT_TO_LONG); + MAP.put(Rops.CONV_D2L, Dops.DOUBLE_TO_LONG); + MAP.put(Rops.CONV_I2F, Dops.INT_TO_FLOAT); + MAP.put(Rops.CONV_L2F, Dops.LONG_TO_FLOAT); + MAP.put(Rops.CONV_D2F, Dops.DOUBLE_TO_FLOAT); + MAP.put(Rops.CONV_I2D, Dops.INT_TO_DOUBLE); + MAP.put(Rops.CONV_L2D, Dops.LONG_TO_DOUBLE); + MAP.put(Rops.CONV_F2D, Dops.FLOAT_TO_DOUBLE); + MAP.put(Rops.TO_BYTE, Dops.INT_TO_BYTE); + MAP.put(Rops.TO_CHAR, Dops.INT_TO_CHAR); + MAP.put(Rops.TO_SHORT, Dops.INT_TO_SHORT); + MAP.put(Rops.RETURN_VOID, Dops.RETURN_VOID); + MAP.put(Rops.RETURN_INT, Dops.RETURN); + MAP.put(Rops.RETURN_LONG, Dops.RETURN_WIDE); + MAP.put(Rops.RETURN_FLOAT, Dops.RETURN); + MAP.put(Rops.RETURN_DOUBLE, Dops.RETURN_WIDE); + MAP.put(Rops.RETURN_OBJECT, Dops.RETURN_OBJECT); + MAP.put(Rops.ARRAY_LENGTH, Dops.ARRAY_LENGTH); + MAP.put(Rops.THROW, Dops.THROW); + MAP.put(Rops.MONITOR_ENTER, Dops.MONITOR_ENTER); + MAP.put(Rops.MONITOR_EXIT, Dops.MONITOR_EXIT); + MAP.put(Rops.AGET_INT, Dops.AGET); + MAP.put(Rops.AGET_LONG, Dops.AGET_WIDE); + MAP.put(Rops.AGET_FLOAT, Dops.AGET); + MAP.put(Rops.AGET_DOUBLE, Dops.AGET_WIDE); + MAP.put(Rops.AGET_OBJECT, Dops.AGET_OBJECT); + MAP.put(Rops.AGET_BOOLEAN, Dops.AGET_BOOLEAN); + MAP.put(Rops.AGET_BYTE, Dops.AGET_BYTE); + MAP.put(Rops.AGET_CHAR, Dops.AGET_CHAR); + MAP.put(Rops.AGET_SHORT, Dops.AGET_SHORT); + MAP.put(Rops.APUT_INT, Dops.APUT); + MAP.put(Rops.APUT_LONG, Dops.APUT_WIDE); + MAP.put(Rops.APUT_FLOAT, Dops.APUT); + MAP.put(Rops.APUT_DOUBLE, Dops.APUT_WIDE); + MAP.put(Rops.APUT_OBJECT, Dops.APUT_OBJECT); + MAP.put(Rops.APUT_BOOLEAN, Dops.APUT_BOOLEAN); + MAP.put(Rops.APUT_BYTE, Dops.APUT_BYTE); + MAP.put(Rops.APUT_CHAR, Dops.APUT_CHAR); + MAP.put(Rops.APUT_SHORT, Dops.APUT_SHORT); + MAP.put(Rops.NEW_INSTANCE, Dops.NEW_INSTANCE); + MAP.put(Rops.CHECK_CAST, Dops.CHECK_CAST); + MAP.put(Rops.INSTANCE_OF, Dops.INSTANCE_OF); + + MAP.put(Rops.GET_FIELD_LONG, Dops.IGET_WIDE); + MAP.put(Rops.GET_FIELD_FLOAT, Dops.IGET); + MAP.put(Rops.GET_FIELD_DOUBLE, Dops.IGET_WIDE); + MAP.put(Rops.GET_FIELD_OBJECT, Dops.IGET_OBJECT); + /* + * Note: No map entries for get_field_* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + MAP.put(Rops.GET_STATIC_LONG, Dops.SGET_WIDE); + MAP.put(Rops.GET_STATIC_FLOAT, Dops.SGET); + MAP.put(Rops.GET_STATIC_DOUBLE, Dops.SGET_WIDE); + MAP.put(Rops.GET_STATIC_OBJECT, Dops.SGET_OBJECT); + /* + * Note: No map entries for get_static* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + MAP.put(Rops.PUT_FIELD_LONG, Dops.IPUT_WIDE); + MAP.put(Rops.PUT_FIELD_FLOAT, Dops.IPUT); + MAP.put(Rops.PUT_FIELD_DOUBLE, Dops.IPUT_WIDE); + MAP.put(Rops.PUT_FIELD_OBJECT, Dops.IPUT_OBJECT); + /* + * Note: No map entries for put_field_* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + MAP.put(Rops.PUT_STATIC_LONG, Dops.SPUT_WIDE); + MAP.put(Rops.PUT_STATIC_FLOAT, Dops.SPUT); + MAP.put(Rops.PUT_STATIC_DOUBLE, Dops.SPUT_WIDE); + MAP.put(Rops.PUT_STATIC_OBJECT, Dops.SPUT_OBJECT); + /* + * Note: No map entries for put_static* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + /* + * Note: No map entries for invoke*, new_array, and + * filled_new_array, since they need to be handled specially + * (see dopFor() below). + */ + } + + /** + * Returns the dalvik opcode appropriate for the given register-based + * instruction. + * + * @param insn {@code non-null;} the original instruction + * @return the corresponding dalvik opcode; one of the constants in + * {@link Dops} + */ + public static Dop dopFor(Insn insn) { + Rop rop = insn.getOpcode(); + + /* + * First, just try looking up the rop in the MAP of easy + * cases. + */ + Dop result = MAP.get(rop); + if (result != null) { + return result; + } + + /* + * There was no easy case for the rop, so look up the opcode, and + * do something special for each: + * + * The move_exception, new_array, filled_new_array, and + * invoke* opcodes won't be found in MAP, since they'll each + * have different source and/or result register types / lists. + * + * The get* and put* opcodes for (non-long) integral types + * aren't in the map, since the type signatures aren't + * sufficient to distinguish between the types (the salient + * source or result will always be just "int"). + * + * And const instruction need to distinguish between strings and + * classes. + */ + + switch (rop.getOpcode()) { + case RegOps.MOVE_EXCEPTION: return Dops.MOVE_EXCEPTION; + case RegOps.INVOKE_STATIC: return Dops.INVOKE_STATIC; + case RegOps.INVOKE_VIRTUAL: return Dops.INVOKE_VIRTUAL; + case RegOps.INVOKE_SUPER: return Dops.INVOKE_SUPER; + case RegOps.INVOKE_DIRECT: return Dops.INVOKE_DIRECT; + case RegOps.INVOKE_INTERFACE: return Dops.INVOKE_INTERFACE; + case RegOps.INVOKE_POLYMORPHIC: return Dops.INVOKE_POLYMORPHIC; + case RegOps.NEW_ARRAY: return Dops.NEW_ARRAY; + case RegOps.FILLED_NEW_ARRAY: return Dops.FILLED_NEW_ARRAY; + case RegOps.FILL_ARRAY_DATA: return Dops.FILL_ARRAY_DATA; + case RegOps.MOVE_RESULT: { + RegisterSpec resultReg = insn.getResult(); + + if (resultReg == null) { + return Dops.NOP; + } else { + switch (resultReg.getBasicType()) { + case Type.BT_INT: + case Type.BT_FLOAT: + case Type.BT_BOOLEAN: + case Type.BT_BYTE: + case Type.BT_CHAR: + case Type.BT_SHORT: + return Dops.MOVE_RESULT; + case Type.BT_LONG: + case Type.BT_DOUBLE: + return Dops.MOVE_RESULT_WIDE; + case Type.BT_OBJECT: + return Dops.MOVE_RESULT_OBJECT; + default: { + throw new RuntimeException("Unexpected basic type"); + } + } + } + } + + case RegOps.GET_FIELD: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.IGET_BOOLEAN; + case Type.BT_BYTE: return Dops.IGET_BYTE; + case Type.BT_CHAR: return Dops.IGET_CHAR; + case Type.BT_SHORT: return Dops.IGET_SHORT; + case Type.BT_INT: return Dops.IGET; + } + break; + } + case RegOps.PUT_FIELD: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.IPUT_BOOLEAN; + case Type.BT_BYTE: return Dops.IPUT_BYTE; + case Type.BT_CHAR: return Dops.IPUT_CHAR; + case Type.BT_SHORT: return Dops.IPUT_SHORT; + case Type.BT_INT: return Dops.IPUT; + } + break; + } + case RegOps.GET_STATIC: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.SGET_BOOLEAN; + case Type.BT_BYTE: return Dops.SGET_BYTE; + case Type.BT_CHAR: return Dops.SGET_CHAR; + case Type.BT_SHORT: return Dops.SGET_SHORT; + case Type.BT_INT: return Dops.SGET; + } + break; + } + case RegOps.PUT_STATIC: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.SPUT_BOOLEAN; + case Type.BT_BYTE: return Dops.SPUT_BYTE; + case Type.BT_CHAR: return Dops.SPUT_CHAR; + case Type.BT_SHORT: return Dops.SPUT_SHORT; + case Type.BT_INT: return Dops.SPUT; + } + break; + } + case RegOps.CONST: { + Constant cst = ((ThrowingCstInsn) insn).getConstant(); + if (cst instanceof CstType) { + return Dops.CONST_CLASS; + } else if (cst instanceof CstString) { + return Dops.CONST_STRING; + } + break; + } + } + + throw new RuntimeException("unknown rop: " + rop); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/RopTranslator.java b/dexlib/src/main/java/com/android/dx/dex/code/RopTranslator.java new file mode 100644 index 000000000..d45e1beff --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/RopTranslator.java @@ -0,0 +1,911 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.dex.DexOptions; +import com.android.dx.io.Opcodes; +import com.android.dx.rop.code.BasicBlock; +import com.android.dx.rop.code.BasicBlockList; +import com.android.dx.rop.code.FillArrayDataInsn; +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.InvokePolymorphicInsn; +import com.android.dx.rop.code.LocalVariableInfo; +import com.android.dx.rop.code.PlainCstInsn; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.RegisterSpecSet; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.code.SwitchInsn; +import com.android.dx.rop.code.ThrowingCstInsn; +import com.android.dx.rop.code.ThrowingInsn; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.util.Bits; +import com.android.dx.util.IntList; +import java.util.ArrayList; + +/** + * Translator from {@link RopMethod} to {@link DalvCode}. The {@link + * #translate} method is the thing to call on this class. + */ +public final class RopTranslator { + /** {@code non-null;} options for dex output */ + private final DexOptions dexOptions; + + /** {@code non-null;} method to translate */ + private final RopMethod method; + + /** + * how much position info to preserve; one of the static + * constants in {@link PositionList} + */ + private final int positionInfo; + + /** {@code null-ok;} local variable info to use */ + private final LocalVariableInfo locals; + + /** {@code non-null;} container for all the address objects for the method */ + private final BlockAddresses addresses; + + /** {@code non-null;} list of output instructions in-progress */ + private final OutputCollector output; + + /** {@code non-null;} visitor to use during translation */ + private final TranslationVisitor translationVisitor; + + /** {@code >= 0;} register count for the method */ + private final int regCount; + + /** {@code null-ok;} block output order; becomes non-null in {@link #pickOrder} */ + private int[] order; + + /** size, in register units, of all the parameters to this method */ + private final int paramSize; + + /** + * true if the parameters to this method happen to be in proper order + * at the end of the frame (as the optimizer emits them) + */ + private boolean paramsAreInOrder; + + /** + * Translates a {@link RopMethod}. This may modify the given + * input. + * + * @param method {@code non-null;} the original method + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param locals {@code null-ok;} local variable information to use + * @param paramSize size, in register units, of all the parameters to + * this method + * @param dexOptions {@code non-null;} options for dex output + * @return {@code non-null;} the translated version + */ + public static DalvCode translate(RopMethod method, int positionInfo, + LocalVariableInfo locals, int paramSize, DexOptions dexOptions) { + RopTranslator translator = + new RopTranslator(method, positionInfo, locals, paramSize, dexOptions); + return translator.translateAndGetResult(); + } + + /** + * Constructs an instance. This method is private. Use {@link #translate}. + * + * @param method {@code non-null;} the original method + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param locals {@code null-ok;} local variable information to use + * @param paramSize size, in register units, of all the parameters to + * this method + * @param dexOptions {@code non-null;} options for dex output + */ + private RopTranslator(RopMethod method, int positionInfo, LocalVariableInfo locals, + int paramSize, DexOptions dexOptions) { + this.dexOptions = dexOptions; + this.method = method; + this.positionInfo = positionInfo; + this.locals = locals; + this.addresses = new BlockAddresses(method); + this.paramSize = paramSize; + this.order = null; + this.paramsAreInOrder = calculateParamsAreInOrder(method, paramSize); + + BasicBlockList blocks = method.getBlocks(); + int bsz = blocks.size(); + + /* + * Max possible instructions includes three code address + * objects per basic block (to the first and last instruction, + * and just past the end of the block), and the possibility of + * an extra goto at the end of each basic block. + */ + int maxInsns = (bsz * 3) + blocks.getInstructionCount(); + + if (locals != null) { + /* + * If we're tracking locals, then there's could be another + * extra instruction per block (for the locals state at the + * start of the block) as well as one for each interblock + * local introduction. + */ + maxInsns += bsz + locals.getAssignmentCount(); + } + + /* + * If params are not in order, we will need register space + * for them before this is all over... + */ + this.regCount = blocks.getRegCount() + + (paramsAreInOrder ? 0 : this.paramSize); + + this.output = new OutputCollector(dexOptions, maxInsns, bsz * 3, regCount, paramSize); + + if (locals != null) { + this.translationVisitor = + new LocalVariableAwareTranslationVisitor(output, locals); + } else { + this.translationVisitor = new TranslationVisitor(output); + } + } + + /** + * Checks to see if the move-param instructions that occur in this + * method happen to slot the params in an order at the top of the + * stack frame that matches dalvik's calling conventions. This will + * alway result in "true" for methods that have run through the + * SSA optimizer. + * + * @param paramSize size, in register units, of all the parameters + * to this method + */ + private static boolean calculateParamsAreInOrder(RopMethod method, + final int paramSize) { + final boolean[] paramsAreInOrder = { true }; + final int initialRegCount = method.getBlocks().getRegCount(); + + /* + * We almost could just check the first block here, but the + * {@code cf} layer will put in a second move-param in a + * subsequent block in the case of synchronized methods. + */ + method.getBlocks().forEachInsn(new Insn.BaseVisitor() { + @Override + public void visitPlainCstInsn(PlainCstInsn insn) { + if (insn.getOpcode().getOpcode()== RegOps.MOVE_PARAM) { + int param = + ((CstInteger) insn.getConstant()).getValue(); + + paramsAreInOrder[0] = paramsAreInOrder[0] + && ((initialRegCount - paramSize + param) + == insn.getResult().getReg()); + } + } + }); + + return paramsAreInOrder[0]; + } + + /** + * Does the translation and returns the result. + * + * @return {@code non-null;} the result + */ + private DalvCode translateAndGetResult() { + pickOrder(); + outputInstructions(); + + StdCatchBuilder catches = + new StdCatchBuilder(method, order, addresses); + + return new DalvCode(positionInfo, output.getFinisher(), catches); + } + + /** + * Performs initial creation of output instructions based on the + * original blocks. + */ + private void outputInstructions() { + BasicBlockList blocks = method.getBlocks(); + int[] order = this.order; + int len = order.length; + + // Process the blocks in output order. + for (int i = 0; i < len; i++) { + int nextI = i + 1; + int nextLabel = (nextI == order.length) ? -1 : order[nextI]; + outputBlock(blocks.labelToBlock(order[i]), nextLabel); + } + } + + /** + * Helper for {@link #outputInstructions}, which does the processing + * and output of one block. + * + * @param block {@code non-null;} the block to process and output + * @param nextLabel {@code >= -1;} the next block that will be processed, or + * {@code -1} if there is no next block + */ + private void outputBlock(BasicBlock block, int nextLabel) { + // Append the code address for this block. + CodeAddress startAddress = addresses.getStart(block); + output.add(startAddress); + + // Append the local variable state for the block. + if (locals != null) { + RegisterSpecSet starts = locals.getStarts(block); + output.add(new LocalSnapshot(startAddress.getPosition(), + starts)); + } + + /* + * Choose and append an output instruction for each original + * instruction. + */ + translationVisitor.setBlock(block, addresses.getLast(block)); + block.getInsns().forEach(translationVisitor); + + // Insert the block end code address. + output.add(addresses.getEnd(block)); + + // Set up for end-of-block activities. + + int succ = block.getPrimarySuccessor(); + Insn lastInsn = block.getLastInsn(); + + /* + * Check for (and possibly correct for) a non-optimal choice of + * which block will get output next. + */ + + if ((succ >= 0) && (succ != nextLabel)) { + /* + * The block has a "primary successor" and that primary + * successor isn't the next block to be output. + */ + Rop lastRop = lastInsn.getOpcode(); + if ((lastRop.getBranchingness() == Rop.BRANCH_IF) && + (block.getSecondarySuccessor() == nextLabel)) { + /* + * The block ends with an "if" of some sort, and its + * secondary successor (the "then") is in fact the + * next block to output. So, reverse the sense of + * the test, so that we can just emit the next block + * without an interstitial goto. + */ + output.reverseBranch(1, addresses.getStart(succ)); + } else { + /* + * Our only recourse is to add a goto here to get the + * flow to be correct. + */ + TargetInsn insn = + new TargetInsn(Dops.GOTO, lastInsn.getPosition(), + RegisterSpecList.EMPTY, + addresses.getStart(succ)); + output.add(insn); + } + } + } + + /** + * Picks an order for the blocks by doing "trace" analysis. + */ + private void pickOrder() { + BasicBlockList blocks = method.getBlocks(); + int sz = blocks.size(); + int maxLabel = blocks.getMaxLabel(); + int[] workSet = Bits.makeBitSet(maxLabel); + int[] tracebackSet = Bits.makeBitSet(maxLabel); + + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + Bits.set(workSet, one.getLabel()); + } + + int[] order = new int[sz]; + int at = 0; + + /* + * Starting with the designated "first label" (that is, the + * first block of the method), add that label to the order, + * and then pick its first as-yet unordered successor to + * immediately follow it, giving top priority to the primary + * (aka default) successor (if any). Keep following successors + * until the trace runs out of possibilities. Then, continue + * by finding an unordered chain containing the first as-yet + * unordered block, and adding it to the order, and so on. + */ + for (int label = method.getFirstLabel(); + label != -1; + label = Bits.findFirst(workSet, 0)) { + + /* + * Attempt to trace backward from the chosen block to an + * as-yet unordered predecessor which lists the chosen + * block as its primary successor, and so on, until we + * fail to find such an unordered predecessor. Start the + * trace with that block. Note that the first block in the + * method has no predecessors, so in that case this loop + * will simply terminate with zero iterations and without + * picking a new starter block. + */ + traceBack: + for (;;) { + IntList preds = method.labelToPredecessors(label); + int psz = preds.size(); + + for (int i = 0; i < psz; i++) { + int predLabel = preds.get(i); + + if (Bits.get(tracebackSet, predLabel)) { + /* + * We found a predecessor loop; stop tracing back + * from here. + */ + break; + } + + if (!Bits.get(workSet, predLabel)) { + // This one's already ordered. + continue; + } + + BasicBlock pred = blocks.labelToBlock(predLabel); + if (pred.getPrimarySuccessor() == label) { + // Found one! + label = predLabel; + Bits.set(tracebackSet, label); + continue traceBack; + } + } + + // Failed to find a better block to start the trace. + break; + } + + /* + * Trace a path from the chosen block to one of its + * unordered successors (hopefully the primary), and so + * on, until we run out of unordered successors. + */ + while (label != -1) { + Bits.clear(workSet, label); + Bits.clear(tracebackSet, label); + order[at] = label; + at++; + + BasicBlock one = blocks.labelToBlock(label); + BasicBlock preferredBlock = blocks.preferredSuccessorOf(one); + + if (preferredBlock == null) { + break; + } + + int preferred = preferredBlock.getLabel(); + int primary = one.getPrimarySuccessor(); + + if (Bits.get(workSet, preferred)) { + /* + * Order the current block's preferred successor + * next, as it has yet to be scheduled. + */ + label = preferred; + } else if ((primary != preferred) && (primary >= 0) + && Bits.get(workSet, primary)) { + /* + * The primary is available, so use that. + */ + label = primary; + } else { + /* + * There's no obvious candidate, so pick the first + * one that's available, if any. + */ + IntList successors = one.getSuccessors(); + int ssz = successors.size(); + label = -1; + for (int i = 0; i < ssz; i++) { + int candidate = successors.get(i); + if (Bits.get(workSet, candidate)) { + label = candidate; + break; + } + } + } + } + } + + if (at != sz) { + // There was a duplicate block label. + throw new RuntimeException("shouldn't happen"); + } + + this.order = order; + } + + /** + * Gets the complete register list (result and sources) out of a + * given rop instruction. For insns that are commutative, have + * two register sources, and have a source equal to the result, + * place that source first. + * + * @param insn {@code non-null;} instruction in question + * @return {@code non-null;} the instruction's complete register list + */ + private static RegisterSpecList getRegs(Insn insn) { + return getRegs(insn, insn.getResult()); + } + + /** + * Gets the complete register list (result and sources) out of a + * given rop instruction. For insns that are commutative, have + * two register sources, and have a source equal to the result, + * place that source first. + * + * @param insn {@code non-null;} instruction in question + * @param resultReg {@code null-ok;} the real result to use (ignore the insn's) + * @return {@code non-null;} the instruction's complete register list + */ + private static RegisterSpecList getRegs(Insn insn, + RegisterSpec resultReg) { + RegisterSpecList regs = insn.getSources(); + + if (insn.getOpcode().isCommutative() + && (regs.size() == 2) + && (resultReg.getReg() == regs.get(1).getReg())) { + + /* + * For commutative ops which have two register sources, + * if the second source is the same register as the result, + * swap the sources so that an opcode of form 12x can be selected + * instead of one of form 23x + */ + + regs = RegisterSpecList.make(regs.get(1), regs.get(0)); + } + + if (resultReg == null) { + return regs; + } + + return regs.withFirst(resultReg); + } + + /** + * Instruction visitor class for doing the instruction translation per se. + */ + private class TranslationVisitor implements Insn.Visitor { + /** {@code non-null;} list of output instructions in-progress */ + private final OutputCollector output; + + /** {@code non-null;} basic block being worked on */ + private BasicBlock block; + + /** + * {@code null-ok;} code address for the salient last instruction of the + * block (used before switches and throwing instructions) + */ + private CodeAddress lastAddress; + + /** + * Constructs an instance. + * + * @param output {@code non-null;} destination for instruction output + */ + public TranslationVisitor(OutputCollector output) { + this.output = output; + } + + /** + * Sets the block currently being worked on. + * + * @param block {@code non-null;} the block + * @param lastAddress {@code non-null;} code address for the salient + * last instruction of the block + */ + public void setBlock(BasicBlock block, CodeAddress lastAddress) { + this.block = block; + this.lastAddress = lastAddress; + } + + /** {@inheritDoc} */ + public void visitPlainInsn(PlainInsn insn) { + Rop rop = insn.getOpcode(); + if (rop.getOpcode() == RegOps.MARK_LOCAL) { + /* + * Ignore these. They're dealt with by + * the LocalVariableAwareTranslationVisitor + */ + return; + } + if (rop.getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + // These get skipped + return; + } + + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + DalvInsn di; + + switch (rop.getBranchingness()) { + case Rop.BRANCH_NONE: + case Rop.BRANCH_RETURN: + case Rop.BRANCH_THROW: { + di = new SimpleInsn(opcode, pos, getRegs(insn)); + break; + } + case Rop.BRANCH_GOTO: { + /* + * Code in the main translation loop will emit a + * goto if necessary (if the branch isn't to the + * immediately subsequent block). + */ + return; + } + case Rop.BRANCH_IF: { + int target = block.getSuccessors().get(1); + di = new TargetInsn(opcode, pos, getRegs(insn), + addresses.getStart(target)); + break; + } + default: { + throw new RuntimeException("shouldn't happen"); + } + } + + addOutput(di); + } + + /** {@inheritDoc} */ + @Override + public void visitPlainCstInsn(PlainCstInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + int ropOpcode = rop.getOpcode(); + DalvInsn di; + + if (rop.getBranchingness() != Rop.BRANCH_NONE) { + throw new RuntimeException("shouldn't happen"); + } + + if (ropOpcode == RegOps.MOVE_PARAM) { + if (!paramsAreInOrder) { + /* + * Parameters are not in order at the top of the reg space. + * We need to add moves. + */ + + RegisterSpec dest = insn.getResult(); + int param = + ((CstInteger) insn.getConstant()).getValue(); + RegisterSpec source = + RegisterSpec.make(regCount - paramSize + param, + dest.getType()); + di = new SimpleInsn(opcode, pos, + RegisterSpecList.make(dest, source)); + addOutput(di); + } + } else { + // No moves required for the parameters + RegisterSpecList regs = getRegs(insn); + di = new CstInsn(opcode, pos, regs, insn.getConstant()); + addOutput(di); + } + } + + /** {@inheritDoc} */ + @Override + public void visitSwitchInsn(SwitchInsn insn) { + SourcePosition pos = insn.getPosition(); + IntList cases = insn.getCases(); + IntList successors = block.getSuccessors(); + int casesSz = cases.size(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessor(); + + /* + * Check the assumptions that the number of cases is one + * less than the number of successors and that the last + * successor in the list is the primary (in this case, the + * default). This test is here to guard against forgetting + * to change this code if the way switch instructions are + * constructed also gets changed. + */ + if ((casesSz != (succSz - 1)) || + (primarySuccessor != successors.get(casesSz))) { + throw new RuntimeException("shouldn't happen"); + } + + CodeAddress[] switchTargets = new CodeAddress[casesSz]; + + for (int i = 0; i < casesSz; i++) { + int label = successors.get(i); + switchTargets[i] = addresses.getStart(label); + } + + CodeAddress dataAddress = new CodeAddress(pos); + // make a new address that binds closely to the switch instruction + CodeAddress switchAddress = + new CodeAddress(lastAddress.getPosition(), true); + SwitchData dataInsn = + new SwitchData(pos, switchAddress, cases, switchTargets); + Dop opcode = dataInsn.isPacked() ? + Dops.PACKED_SWITCH : Dops.SPARSE_SWITCH; + TargetInsn switchInsn = + new TargetInsn(opcode, pos, getRegs(insn), dataAddress); + + addOutput(switchAddress); + addOutput(switchInsn); + + addOutputSuffix(new OddSpacer(pos)); + addOutputSuffix(dataAddress); + addOutputSuffix(dataInsn); + } + + /** + * Looks forward to the current block's primary successor, returning + * the RegisterSpec of the result of the move-result-pseudo at the + * top of that block or null if none. + * + * @return {@code null-ok;} result of move-result-pseudo at the beginning of + * primary successor + */ + private RegisterSpec getNextMoveResultPseudo() + { + int label = block.getPrimarySuccessor(); + + if (label < 0) { + return null; + } + + Insn insn + = method.getBlocks().labelToBlock(label).getInsns().get(0); + + if (insn.getOpcode().getOpcode() != RegOps.MOVE_RESULT_PSEUDO) { + return null; + } else { + return insn.getResult(); + } + } + + /** {@inheritDoc} */ + @Override + public void visitInvokePolymorphicInsn(InvokePolymorphicInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + + if (rop.getBranchingness() != Rop.BRANCH_THROW) { + throw new RuntimeException("Expected BRANCH_THROW got " + rop.getBranchingness()); + } else if (!rop.isCallLike()) { + throw new RuntimeException("Expected call-like operation"); + } + + addOutput(lastAddress); + + RegisterSpecList regs = insn.getSources(); + Constant[] constants = new Constant[] { + insn.getInvokeMethod(), + insn.getCallSiteProto() + }; + DalvInsn di = new MultiCstInsn(opcode, pos, regs, constants); + + addOutput(di); + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + Constant cst = insn.getConstant(); + + if (rop.getBranchingness() != Rop.BRANCH_THROW) { + throw new RuntimeException("Expected BRANCH_THROW got " + rop.getBranchingness()); + } + + addOutput(lastAddress); + + if (rop.isCallLike()) { + RegisterSpecList regs = insn.getSources(); + DalvInsn di = new CstInsn(opcode, pos, regs, cst); + + addOutput(di); + } else { + RegisterSpec realResult = getNextMoveResultPseudo(); + + RegisterSpecList regs = getRegs(insn, realResult); + DalvInsn di; + + boolean hasResult = opcode.hasResult() + || (rop.getOpcode() == RegOps.CHECK_CAST); + + if (hasResult != (realResult != null)) { + throw new RuntimeException( + "Insn with result/move-result-pseudo mismatch " + + insn); + } + + if ((rop.getOpcode() == RegOps.NEW_ARRAY) && + (opcode.getOpcode() != Opcodes.NEW_ARRAY)) { + /* + * It's a type-specific new-array-, and + * so it should be turned into a SimpleInsn (no + * constant ref as it's implicit). + */ + di = new SimpleInsn(opcode, pos, regs); + } else { + /* + * This is the general case for constant-bearing + * instructions. + */ + di = new CstInsn(opcode, pos, regs, cst); + } + + addOutput(di); + } + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingInsn(ThrowingInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + RegisterSpec realResult; + + if (rop.getBranchingness() != Rop.BRANCH_THROW) { + throw new RuntimeException("shouldn't happen"); + } + + realResult = getNextMoveResultPseudo(); + + if (opcode.hasResult() != (realResult != null)) { + throw new RuntimeException( + "Insn with result/move-result-pseudo mismatch" + insn); + } + + addOutput(lastAddress); + + DalvInsn di = new SimpleInsn(opcode, pos, + getRegs(insn, realResult)); + + addOutput(di); + } + + /** {@inheritDoc} */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + SourcePosition pos = insn.getPosition(); + Constant cst = insn.getConstant(); + ArrayList values = insn.getInitValues(); + Rop rop = insn.getOpcode(); + + if (rop.getBranchingness() != Rop.BRANCH_NONE) { + throw new RuntimeException("shouldn't happen"); + } + CodeAddress dataAddress = new CodeAddress(pos); + ArrayData dataInsn = + new ArrayData(pos, lastAddress, values, cst); + + TargetInsn fillArrayDataInsn = + new TargetInsn(Dops.FILL_ARRAY_DATA, pos, getRegs(insn), + dataAddress); + + addOutput(lastAddress); + addOutput(fillArrayDataInsn); + + addOutputSuffix(new OddSpacer(pos)); + addOutputSuffix(dataAddress); + addOutputSuffix(dataInsn); + } + + /** + * Adds to the output. + * + * @param insn {@code non-null;} instruction to add + */ + protected void addOutput(DalvInsn insn) { + output.add(insn); + } + + /** + * Adds to the output suffix. + * + * @param insn {@code non-null;} instruction to add + */ + protected void addOutputSuffix(DalvInsn insn) { + output.addSuffix(insn); + } + } + + /** + * Instruction visitor class for doing instruction translation with + * local variable tracking + */ + private class LocalVariableAwareTranslationVisitor + extends TranslationVisitor { + /** {@code non-null;} local variable info */ + private LocalVariableInfo locals; + + /** + * Constructs an instance. + * + * @param output {@code non-null;} destination for instruction output + * @param locals {@code non-null;} the local variable info + */ + public LocalVariableAwareTranslationVisitor(OutputCollector output, + LocalVariableInfo locals) { + super(output); + this.locals = locals; + } + + /** {@inheritDoc} */ + @Override + public void visitPlainInsn(PlainInsn insn) { + super.visitPlainInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitPlainCstInsn(PlainCstInsn insn) { + super.visitPlainCstInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitSwitchInsn(SwitchInsn insn) { + super.visitSwitchInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + super.visitThrowingCstInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingInsn(ThrowingInsn insn) { + super.visitThrowingInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** + * Adds a {@link LocalStart} to the output if the given + * instruction in fact introduces a local variable. + * + * @param insn {@code non-null;} instruction in question + */ + public void addIntroductionIfNecessary(Insn insn) { + RegisterSpec spec = locals.getAssignment(insn); + + if (spec != null) { + addOutput(new LocalStart(insn.getPosition(), spec)); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/SimpleInsn.java b/dexlib/src/main/java/com/android/dx/dex/code/SimpleInsn.java new file mode 100644 index 000000000..8cdcc5563 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/SimpleInsn.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; + +/** + * Instruction which has no extra info beyond the basics provided for in + * the base class. + */ +public final class SimpleInsn extends FixedSizeInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + */ + public SimpleInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers) { + super(opcode, position, registers); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + return new SimpleInsn(opcode, getPosition(), getRegisters()); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new SimpleInsn(getOpcode(), getPosition(), registers); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/StdCatchBuilder.java b/dexlib/src/main/java/com/android/dx/dex/code/StdCatchBuilder.java new file mode 100644 index 000000000..afac94669 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/StdCatchBuilder.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.BasicBlock; +import com.android.dx.rop.code.BasicBlockList; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.IntList; +import java.util.ArrayList; +import java.util.HashSet; + +/** + * Constructor of {@link CatchTable} instances from {@link RopMethod} + * and associated data. + */ +public final class StdCatchBuilder implements CatchBuilder { + /** the maximum range of a single catch handler, in code units */ + private static final int MAX_CATCH_RANGE = 65535; + + /** {@code non-null;} method to build the list for */ + private final RopMethod method; + + /** {@code non-null;} block output order */ + private final int[] order; + + /** {@code non-null;} address objects for each block */ + private final BlockAddresses addresses; + + /** + * Constructs an instance. It merely holds onto its parameters for + * a subsequent call to {@link #build}. + * + * @param method {@code non-null;} method to build the list for + * @param order {@code non-null;} block output order + * @param addresses {@code non-null;} address objects for each block + */ + public StdCatchBuilder(RopMethod method, int[] order, + BlockAddresses addresses) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (order == null) { + throw new NullPointerException("order == null"); + } + + if (addresses == null) { + throw new NullPointerException("addresses == null"); + } + + this.method = method; + this.order = order; + this.addresses = addresses; + } + + /** {@inheritDoc} */ + public CatchTable build() { + return build(method, order, addresses); + } + + /** {@inheritDoc} */ + public boolean hasAnyCatches() { + BasicBlockList blocks = method.getBlocks(); + int size = blocks.size(); + + for (int i = 0; i < size; i++) { + BasicBlock block = blocks.get(i); + TypeList catches = block.getLastInsn().getCatches(); + if (catches.size() != 0) { + return true; + } + } + + return false; + } + + /** {@inheritDoc} */ + public HashSet getCatchTypes() { + HashSet result = new HashSet(20); + BasicBlockList blocks = method.getBlocks(); + int size = blocks.size(); + + for (int i = 0; i < size; i++) { + BasicBlock block = blocks.get(i); + TypeList catches = block.getLastInsn().getCatches(); + int catchSize = catches.size(); + + for (int j = 0; j < catchSize; j++) { + result.add(catches.getType(j)); + } + } + + return result; + } + + /** + * Builds and returns the catch table for a given method. + * + * @param method {@code non-null;} method to build the list for + * @param order {@code non-null;} block output order + * @param addresses {@code non-null;} address objects for each block + * @return {@code non-null;} the constructed table + */ + public static CatchTable build(RopMethod method, int[] order, + BlockAddresses addresses) { + int len = order.length; + BasicBlockList blocks = method.getBlocks(); + ArrayList resultList = + new ArrayList(len); + CatchHandlerList currentHandlers = CatchHandlerList.EMPTY; + BasicBlock currentStartBlock = null; + BasicBlock currentEndBlock = null; + + for (int i = 0; i < len; i++) { + BasicBlock block = blocks.labelToBlock(order[i]); + + if (!block.canThrow()) { + /* + * There is no need to concern ourselves with the + * placement of blocks that can't throw with respect + * to the blocks that *can* throw. + */ + continue; + } + + CatchHandlerList handlers = handlersFor(block, addresses); + + if (currentHandlers.size() == 0) { + // This is the start of a new catch range. + currentStartBlock = block; + currentEndBlock = block; + currentHandlers = handlers; + continue; + } + + if (currentHandlers.equals(handlers) + && rangeIsValid(currentStartBlock, block, addresses)) { + /* + * The block we are looking at now has the same handlers + * as the block that started the currently open catch + * range, and adding it to the currently open range won't + * cause it to be too long. + */ + currentEndBlock = block; + continue; + } + + /* + * The block we are looking at now has incompatible handlers, + * so we need to finish off the last entry and start a new + * one. Note: We only emit an entry if it has associated handlers. + */ + if (currentHandlers.size() != 0) { + CatchTable.Entry entry = + makeEntry(currentStartBlock, currentEndBlock, + currentHandlers, addresses); + resultList.add(entry); + } + + currentStartBlock = block; + currentEndBlock = block; + currentHandlers = handlers; + } + + if (currentHandlers.size() != 0) { + // Emit an entry for the range that was left hanging. + CatchTable.Entry entry = + makeEntry(currentStartBlock, currentEndBlock, + currentHandlers, addresses); + resultList.add(entry); + } + + // Construct the final result. + + int resultSz = resultList.size(); + + if (resultSz == 0) { + return CatchTable.EMPTY; + } + + CatchTable result = new CatchTable(resultSz); + + for (int i = 0; i < resultSz; i++) { + result.set(i, resultList.get(i)); + } + + result.setImmutable(); + return result; + } + + /** + * Makes the {@link CatchHandlerList} for the given basic block. + * + * @param block {@code non-null;} block to get entries for + * @param addresses {@code non-null;} address objects for each block + * @return {@code non-null;} array of entries + */ + private static CatchHandlerList handlersFor(BasicBlock block, + BlockAddresses addresses) { + IntList successors = block.getSuccessors(); + int succSize = successors.size(); + int primary = block.getPrimarySuccessor(); + TypeList catches = block.getLastInsn().getCatches(); + int catchSize = catches.size(); + + if (catchSize == 0) { + return CatchHandlerList.EMPTY; + } + + if (((primary == -1) && (succSize != catchSize)) + || ((primary != -1) && + ((succSize != (catchSize + 1)) + || (primary != successors.get(catchSize))))) { + /* + * Blocks that throw are supposed to list their primary + * successor -- if any -- last in the successors list, but + * that constraint appears to be violated here. + */ + throw new RuntimeException( + "shouldn't happen: weird successors list"); + } + + /* + * Reduce the effective catchSize if we spot a catch-all that + * isn't at the end. + */ + for (int i = 0; i < catchSize; i++) { + Type type = catches.getType(i); + if (type.equals(Type.OBJECT)) { + catchSize = i + 1; + break; + } + } + + CatchHandlerList result = new CatchHandlerList(catchSize); + + for (int i = 0; i < catchSize; i++) { + CstType oneType = new CstType(catches.getType(i)); + CodeAddress oneHandler = addresses.getStart(successors.get(i)); + result.set(i, oneType, oneHandler.getAddress()); + } + + result.setImmutable(); + return result; + } + + /** + * Makes a {@link CatchTable#Entry} for the given block range and + * handlers. + * + * @param start {@code non-null;} the start block for the range (inclusive) + * @param end {@code non-null;} the start block for the range (also inclusive) + * @param handlers {@code non-null;} the handlers for the range + * @param addresses {@code non-null;} address objects for each block + */ + private static CatchTable.Entry makeEntry(BasicBlock start, + BasicBlock end, CatchHandlerList handlers, + BlockAddresses addresses) { + /* + * We start at the *last* instruction of the start block, since + * that's the instruction that can throw... + */ + CodeAddress startAddress = addresses.getLast(start); + + // ...And we end *after* the last instruction of the end block. + CodeAddress endAddress = addresses.getEnd(end); + + return new CatchTable.Entry(startAddress.getAddress(), + endAddress.getAddress(), handlers); + } + + /** + * Gets whether the address range for the given two blocks is valid + * for a catch handler. This is true as long as the covered range is + * under 65536 code units. + * + * @param start {@code non-null;} the start block for the range (inclusive) + * @param end {@code non-null;} the start block for the range (also inclusive) + * @param addresses {@code non-null;} address objects for each block + * @return {@code true} if the range is valid as a catch range + */ + private static boolean rangeIsValid(BasicBlock start, BasicBlock end, + BlockAddresses addresses) { + if (start == null) { + throw new NullPointerException("start == null"); + } + + if (end == null) { + throw new NullPointerException("end == null"); + } + + // See above about selection of instructions. + int startAddress = addresses.getLast(start).getAddress(); + int endAddress = addresses.getEnd(end).getAddress(); + + return (endAddress - startAddress) <= MAX_CATCH_RANGE; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/SwitchData.java b/dexlib/src/main/java/com/android/dx/dex/code/SwitchData.java new file mode 100644 index 000000000..8fc80b1ca --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/SwitchData.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.io.Opcodes; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import com.android.dx.util.IntList; + +/** + * Pseudo-instruction which holds switch data. The switch data is + * a map of values to target addresses, and this class writes the data + * in either a "packed" or "sparse" form. + */ +public final class SwitchData extends VariableSizeInsn { + /** + * {@code non-null;} address representing the instruction that uses this + * instance + */ + private final CodeAddress user; + + /** {@code non-null;} sorted list of switch cases (keys) */ + private final IntList cases; + + /** + * {@code non-null;} corresponding list of code addresses; the branch + * target for each case + */ + private final CodeAddress[] targets; + + /** whether the output table will be packed (vs. sparse) */ + private final boolean packed; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param user {@code non-null;} address representing the instruction that + * uses this instance + * @param cases {@code non-null;} sorted list of switch cases (keys) + * @param targets {@code non-null;} corresponding list of code addresses; the + * branch target for each case + */ + public SwitchData(SourcePosition position, CodeAddress user, + IntList cases, CodeAddress[] targets) { + super(position, RegisterSpecList.EMPTY); + + if (user == null) { + throw new NullPointerException("user == null"); + } + + if (cases == null) { + throw new NullPointerException("cases == null"); + } + + if (targets == null) { + throw new NullPointerException("targets == null"); + } + + int sz = cases.size(); + + if (sz != targets.length) { + throw new IllegalArgumentException("cases / targets mismatch"); + } + + if (sz > 65535) { + throw new IllegalArgumentException("too many cases"); + } + + this.user = user; + this.cases = cases; + this.targets = targets; + this.packed = shouldPack(cases); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return packed ? (int) packedCodeSize(cases) : + (int) sparseCodeSize(cases); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + int baseAddress = user.getAddress(); + int defaultTarget = Dops.PACKED_SWITCH.getFormat().codeSize(); + int sz = targets.length; + + if (packed) { + int firstCase = (sz == 0) ? 0 : cases.get(0); + int lastCase = (sz == 0) ? 0 : cases.get(sz - 1); + int outSz = lastCase - firstCase + 1; + + out.writeShort(Opcodes.PACKED_SWITCH_PAYLOAD); + out.writeShort(outSz); + out.writeInt(firstCase); + + int caseAt = 0; + for (int i = 0; i < outSz; i++) { + int outCase = firstCase + i; + int oneCase = cases.get(caseAt); + int relTarget; + + if (oneCase > outCase) { + relTarget = defaultTarget; + } else { + relTarget = targets[caseAt].getAddress() - baseAddress; + caseAt++; + } + + out.writeInt(relTarget); + } + } else { + out.writeShort(Opcodes.SPARSE_SWITCH_PAYLOAD); + out.writeShort(sz); + + for (int i = 0; i < sz; i++) { + out.writeInt(cases.get(i)); + } + + for (int i = 0; i < sz; i++) { + int relTarget = targets[i].getAddress() - baseAddress; + out.writeInt(relTarget); + } + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new SwitchData(getPosition(), user, cases, targets); + } + + /** + * Returns whether or not this instance's data will be output as packed. + * + * @return {@code true} iff the data is to be packed + */ + public boolean isPacked() { + return packed; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + StringBuffer sb = new StringBuffer(100); + + int sz = targets.length; + for (int i = 0; i < sz; i++) { + sb.append("\n "); + sb.append(cases.get(i)); + sb.append(": "); + sb.append(targets[i]); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + int baseAddress = user.getAddress(); + StringBuffer sb = new StringBuffer(100); + int sz = targets.length; + + sb.append(packed ? "packed" : "sparse"); + sb.append("-switch-payload // for switch @ "); + sb.append(Hex.u2(baseAddress)); + + for (int i = 0; i < sz; i++) { + int absTarget = targets[i].getAddress(); + int relTarget = absTarget - baseAddress; + sb.append("\n "); + sb.append(cases.get(i)); + sb.append(": "); + sb.append(Hex.u4(absTarget)); + sb.append(" // "); + sb.append(Hex.s4(relTarget)); + } + + return sb.toString(); + } + + /** + * Gets the size of a packed table for the given cases, in 16-bit code + * units. + * + * @param cases {@code non-null;} sorted list of cases + * @return {@code >= -1;} the packed table size or {@code -1} if the + * cases couldn't possibly be represented as a packed table + */ + private static long packedCodeSize(IntList cases) { + int sz = cases.size(); + long low = cases.get(0); + long high = cases.get(sz - 1); + long result = ((high - low + 1)) * 2 + 4; + + return (result <= 0x7fffffff) ? result : -1; + } + + /** + * Gets the size of a sparse table for the given cases, in 16-bit code + * units. + * + * @param cases {@code non-null;} sorted list of cases + * @return {@code > 0;} the sparse table size + */ + private static long sparseCodeSize(IntList cases) { + int sz = cases.size(); + + return (sz * 4L) + 2; + } + + /** + * Determines whether the given list of cases warrant being packed. + * + * @param cases {@code non-null;} sorted list of cases + * @return {@code true} iff the table encoding the cases + * should be packed + */ + private static boolean shouldPack(IntList cases) { + int sz = cases.size(); + + if (sz < 2) { + return true; + } + + long packedSize = packedCodeSize(cases); + long sparseSize = sparseCodeSize(cases); + + /* + * We pick the packed representation if it is possible and + * would be as small or smaller than 5/4 of the sparse + * representation. That is, we accept some size overhead on + * the packed representation, since that format is faster to + * execute at runtime. + */ + return (packedSize >= 0) && (packedSize <= ((sparseSize * 5) / 4)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/TargetInsn.java b/dexlib/src/main/java/com/android/dx/dex/code/TargetInsn.java new file mode 100644 index 000000000..cbb5ff9a6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/TargetInsn.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; + +/** + * Instruction which has a single branch target. + */ +public final class TargetInsn extends FixedSizeInsn { + /** {@code non-null;} the branch target */ + private CodeAddress target; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}), and the target is initially + * {@code null}. + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + * @param target {@code non-null;} the branch target + */ + public TargetInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers, CodeAddress target) { + super(opcode, position, registers); + + if (target == null) { + throw new NullPointerException("target == null"); + } + + this.target = target; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + return new TargetInsn(opcode, getPosition(), getRegisters(), target); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new TargetInsn(getOpcode(), getPosition(), registers, target); + } + + /** + * Returns an instance that is just like this one, except that its + * opcode has the opposite sense (as a test; e.g. a + * {@code lt} test becomes a {@code ge}), and its branch + * target is replaced by the one given, and all set-once values + * associated with the class (such as its address) are reset. + * + * @param target {@code non-null;} the new branch target + * @return {@code non-null;} an appropriately-constructed instance + */ + public TargetInsn withNewTargetAndReversed(CodeAddress target) { + Dop opcode = getOpcode().getOppositeTest(); + + return new TargetInsn(opcode, getPosition(), getRegisters(), target); + } + + /** + * Gets the unique branch target of this instruction. + * + * @return {@code non-null;} the branch target + */ + public CodeAddress getTarget() { + return target; + } + + /** + * Gets the target address of this instruction. This is only valid + * to call if the target instruction has been assigned an address, + * and it is merely a convenient shorthand for + * {@code getTarget().getAddress()}. + * + * @return {@code >= 0;} the target address + */ + public int getTargetAddress() { + return target.getAddress(); + } + + /** + * Gets the branch offset of this instruction. This is only valid to + * call if both this and the target instruction each has been assigned + * an address, and it is merely a convenient shorthand for + * {@code getTargetAddress() - getAddress()}. + * + * @return the branch offset + */ + public int getTargetOffset() { + return target.getAddress() - getAddress(); + } + + /** + * Returns whether the target offset is known. + * + * @return {@code true} if the target offset is known or + * {@code false} if not + */ + public boolean hasTargetOffset() { + return hasAddress() && target.hasAddress(); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + if (target == null) { + return "????"; + } + + return target.identifierString(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/VariableSizeInsn.java b/dexlib/src/main/java/com/android/dx/dex/code/VariableSizeInsn.java new file mode 100644 index 000000000..06b40f7a8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/VariableSizeInsn.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; + +/** + * Pseudo-instruction base class for variable-sized instructions. + */ +public abstract class VariableSizeInsn extends DalvInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param registers {@code non-null;} source registers + */ + public VariableSizeInsn(SourcePosition position, + RegisterSpecList registers) { + super(Dops.SPECIAL_FORMAT, position, registers); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withOpcode(Dop opcode) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withRegisterOffset(int delta) { + return withRegisters(getRegisters().withOffset(delta)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/ZeroSizeInsn.java b/dexlib/src/main/java/com/android/dx/dex/code/ZeroSizeInsn.java new file mode 100644 index 000000000..2cc157b7a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/ZeroSizeInsn.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code; + +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.util.AnnotatedOutput; + +/** + * Pseudo-instruction base class for zero-size (no code emitted) + * instructions, which are generally used for tracking metainformation + * about the code they are adjacent to. + */ +public abstract class ZeroSizeInsn extends DalvInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + */ + public ZeroSizeInsn(SourcePosition position) { + super(Dops.SPECIAL_FORMAT, position, RegisterSpecList.EMPTY); + } + + /** {@inheritDoc} */ + @Override + public final int codeSize() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(AnnotatedOutput out) { + // Nothing to do here, for this class. + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withOpcode(Dop opcode) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return withRegisters(getRegisters().withOffset(delta)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form10t.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form10t.java new file mode 100644 index 000000000..ced4a6400 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form10t.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.TargetInsn; +import com.android.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 10t}. See the instruction format spec + * for details. + */ +public final class Form10t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form10t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form10t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!((insn instanceof TargetInsn) && + (insn.getRegisters().size() == 0))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInByte(offset); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, (offset & 0xff))); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form10x.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form10x.java new file mode 100644 index 000000000..4be3aa09d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form10x.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.SimpleInsn; +import com.android.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 10x}. See the instruction format spec + * for details. + */ +public final class Form10x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form10x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form10x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + // This format has no arguments. + return ""; + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + return (insn instanceof SimpleInsn) && + (insn.getRegisters().size() == 0); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + write(out, opcodeUnit(insn, 0)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form11n.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form11n.java new file mode 100644 index 000000000..f83f33198 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form11n.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 11n}. See the instruction format spec + * for details. + */ +public final class Form11n extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form11n(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form11n() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 4); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInNibble(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInNibble(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInNibble(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, makeByte(regs.get(0).getReg(), value & 0xf))); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form11x.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form11x.java new file mode 100644 index 000000000..cb1fd0d3f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form11x.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.SimpleInsn; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 11x}. See the instruction format spec + * for details. + */ +public final class Form11x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form11x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form11x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return (insn instanceof SimpleInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + write(out, opcodeUnit(insn, regs.get(0).getReg())); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form12x.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form12x.java new file mode 100644 index 000000000..a2e88e338 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form12x.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.SimpleInsn; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 12x}. See the instruction format spec + * for details. + */ +public final class Form12x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form12x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form12x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + + /* + * The (sz - 2) and (sz - 1) below makes this code work for + * both the two- and three-register ops. (See "case 3" in + * isCompatible(), below.) + */ + + return regs.get(sz - 2).regString() + ", " + + regs.get(sz - 1).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof SimpleInsn)) { + return false; + } + + RegisterSpecList regs = insn.getRegisters(); + RegisterSpec rs1; + RegisterSpec rs2; + + switch (regs.size()) { + case 2: { + rs1 = regs.get(0); + rs2 = regs.get(1); + break; + } + case 3: { + /* + * This format is allowed for ops that are effectively + * 3-arg but where the first two args are identical. + */ + rs1 = regs.get(1); + rs2 = regs.get(2); + if (rs1.getReg() != regs.get(0).getReg()) { + return false; + } + break; + } + default: { + return false; + } + } + + return unsignedFitsInNibble(rs1.getReg()) && + unsignedFitsInNibble(rs2.getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + int r0 = regs.get(0).getReg(); + int r1 = regs.get(1).getReg(); + + switch (regs.size()) { + case 2: { + bits.set(0, unsignedFitsInNibble(r0)); + bits.set(1, unsignedFitsInNibble(r1)); + break; + } + case 3: { + if (r0 != r1) { + bits.set(0, false); + bits.set(1, false); + } else { + boolean dstRegComp = unsignedFitsInNibble(r1); + bits.set(0, dstRegComp); + bits.set(1, dstRegComp); + } + + bits.set(2, unsignedFitsInNibble(regs.get(2).getReg())); + break; + } + default: { + throw new AssertionError(); + } + } + + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + + /* + * The (sz - 2) and (sz - 1) below makes this code work for + * both the two- and three-register ops. (See "case 3" in + * isCompatible(), above.) + */ + + write(out, opcodeUnit(insn, + makeByte(regs.get(sz - 2).getReg(), + regs.get(sz - 1).getReg()))); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form20t.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form20t.java new file mode 100644 index 000000000..a19ed2891 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form20t.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.TargetInsn; +import com.android.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 20t}. See the instruction format spec + * for details. + */ +public final class Form20t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form20t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form20t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!((insn instanceof TargetInsn) && + (insn.getRegisters().size() == 0))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInShort(offset); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, 0), (short) offset); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form21c.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form21c.java new file mode 100644 index 000000000..9d074aa1d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form21c.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 21c}. See the instruction format spec + * for details. + */ +public final class Form21c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21c(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + insn.cstString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return insn.cstComment(); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + RegisterSpecList regs = insn.getRegisters(); + RegisterSpec reg; + + switch (regs.size()) { + case 1: { + reg = regs.get(0); + break; + } + case 2: { + /* + * This format is allowed for ops that are effectively + * 2-arg but where the two args are identical. + */ + reg = regs.get(0); + if (reg.getReg() != regs.get(1).getReg()) { + return false; + } + break; + } + default: { + return false; + } + } + + if (!unsignedFitsInByte(reg.getReg())) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + Constant cst = ci.getConstant(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + return (cst instanceof CstType) || + (cst instanceof CstFieldRef) || + (cst instanceof CstString); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + BitSet bits = new BitSet(sz); + boolean compat = unsignedFitsInByte(regs.get(0).getReg()); + + if (sz == 1) { + bits.set(0, compat); + } else { + if (regs.get(0).getReg() == regs.get(1).getReg()) { + bits.set(0, compat); + bits.set(1, compat); + } + } + + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) cpi); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form21h.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form21h.java new file mode 100644 index 000000000..ea51cb8dd --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form21h.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 21h}. See the instruction format spec + * for details. + */ +public final class Form21h extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21h(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21h() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return + literalBitsComment(value, + (regs.get(0).getCategory() == 1) ? 32 : 64); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + // Where the high bits are depends on the category of the target. + if (regs.get(0).getCategory() == 1) { + int bits = cb.getIntBits(); + return ((bits & 0xffff) == 0); + } else { + long bits = cb.getLongBits(); + return ((bits & 0xffffffffffffL) == 0); + } + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits cb = (CstLiteralBits) ((CstInsn) insn).getConstant(); + short bits; + + // Where the high bits are depends on the category of the target. + if (regs.get(0).getCategory() == 1) { + bits = (short) (cb.getIntBits() >>> 16); + } else { + bits = (short) (cb.getLongBits() >>> 48); + } + + write(out, opcodeUnit(insn, regs.get(0).getReg()), bits); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form21s.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form21s.java new file mode 100644 index 000000000..76f9824d1 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form21s.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 21s}. See the instruction format spec + * for details. + */ +public final class Form21s extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21s(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21s() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 16); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInShort(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) value); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form21t.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form21t.java new file mode 100644 index 000000000..9d31dc5e4 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form21t.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.TargetInsn; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 21t}. See the instruction format spec + * for details. + */ +public final class Form21t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof TargetInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInShort(offset); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) offset); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form22b.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22b.java new file mode 100644 index 000000000..006308bb6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22b.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 22b}. See the instruction format spec + * for details. + */ +public final class Form22b extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22b(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22b() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 8); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 2) && + unsignedFitsInByte(regs.get(0).getReg()) && + unsignedFitsInByte(regs.get(1).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInByte(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + bits.set(1, unsignedFitsInByte(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + codeUnit(regs.get(1).getReg(), value & 0xff)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form22c.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22c.java new file mode 100644 index 000000000..c964cd86c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22c.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 22c}. See the instruction format spec + * for details. + */ +public final class Form22c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22c(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + insn.cstString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return insn.cstComment(); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 2) && + unsignedFitsInNibble(regs.get(0).getReg()) && + unsignedFitsInNibble(regs.get(1).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + Constant cst = ci.getConstant(); + return (cst instanceof CstType) || + (cst instanceof CstFieldRef); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInNibble(regs.get(0).getReg())); + bits.set(1, unsignedFitsInNibble(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + + write(out, + opcodeUnit(insn, + makeByte(regs.get(0).getReg(), regs.get(1).getReg())), + (short) cpi); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form22s.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22s.java new file mode 100644 index 000000000..a48c367c8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22s.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 22s}. See the instruction format spec + * for details. + */ +public final class Form22s extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22s(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22s() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 16); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 2) && + unsignedFitsInNibble(regs.get(0).getReg()) && + unsignedFitsInNibble(regs.get(1).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInShort(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInNibble(regs.get(0).getReg())); + bits.set(1, unsignedFitsInNibble(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, + makeByte(regs.get(0).getReg(), regs.get(1).getReg())), + (short) value); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form22t.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22t.java new file mode 100644 index 000000000..f67608f1c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22t.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.TargetInsn; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 22t}. See the instruction format spec + * for details. + */ +public final class Form22t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof TargetInsn) && + (regs.size() == 2) && + unsignedFitsInNibble(regs.get(0).getReg()) && + unsignedFitsInNibble(regs.get(1).getReg()))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInNibble(regs.get(0).getReg())); + bits.set(1, unsignedFitsInNibble(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInShort(offset); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, + opcodeUnit(insn, + makeByte(regs.get(0).getReg(), regs.get(1).getReg())), + (short) offset); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form22x.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22x.java new file mode 100644 index 000000000..fedc9117b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form22x.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.SimpleInsn; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 22x}. See the instruction format spec + * for details. + */ +public final class Form22x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + return (insn instanceof SimpleInsn) && + (regs.size() == 2) && + unsignedFitsInByte(regs.get(0).getReg()) && + unsignedFitsInShort(regs.get(1).getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + bits.set(1, unsignedFitsInShort(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) regs.get(1).getReg()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form23x.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form23x.java new file mode 100644 index 000000000..4e11ab899 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form23x.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.SimpleInsn; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 23x}. See the instruction format spec + * for details. + */ +public final class Form23x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form23x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form23x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + regs.get(2).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + return (insn instanceof SimpleInsn) && + (regs.size() == 3) && + unsignedFitsInByte(regs.get(0).getReg()) && + unsignedFitsInByte(regs.get(1).getReg()) && + unsignedFitsInByte(regs.get(2).getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(3); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + bits.set(1, unsignedFitsInByte(regs.get(1).getReg())); + bits.set(2, unsignedFitsInByte(regs.get(2).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + codeUnit(regs.get(1).getReg(), regs.get(2).getReg())); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form30t.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form30t.java new file mode 100644 index 000000000..86a3e828a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form30t.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.TargetInsn; +import com.android.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 30t}. See the instruction format spec + * for details. + */ +public final class Form30t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form30t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form30t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!((insn instanceof TargetInsn) && + (insn.getRegisters().size() == 0))) { + return false; + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + return true; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, 0), offset); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form31c.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form31c.java new file mode 100644 index 000000000..bf64f273a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form31c.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 31c}. See the instruction format spec + * for details. + */ +public final class Form31c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form31c(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form31c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + insn.cstString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return insn.cstComment(); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + RegisterSpecList regs = insn.getRegisters(); + RegisterSpec reg; + + switch (regs.size()) { + case 1: { + reg = regs.get(0); + break; + } + case 2: { + /* + * This format is allowed for ops that are effectively + * 2-arg but where the two args are identical. + */ + reg = regs.get(0); + if (reg.getReg() != regs.get(1).getReg()) { + return false; + } + break; + } + default: { + return false; + } + } + + if (!unsignedFitsInByte(reg.getReg())) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + return (cst instanceof CstType) || + (cst instanceof CstFieldRef) || + (cst instanceof CstString); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + BitSet bits = new BitSet(sz); + boolean compat = unsignedFitsInByte(regs.get(0).getReg()); + + if (sz == 1) { + bits.set(0, compat); + } else { + if (regs.get(0).getReg() == regs.get(1).getReg()) { + bits.set(0, compat); + bits.set(1, compat); + } + } + + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + + write(out, opcodeUnit(insn, regs.get(0).getReg()), cpi); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form31i.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form31i.java new file mode 100644 index 000000000..a05911158 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form31i.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 31i}. See the instruction format spec + * for details. + */ +public final class Form31i extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form31i(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form31i() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 32); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + return ((CstLiteralBits) cst).fitsInInt(); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, opcodeUnit(insn, regs.get(0).getReg()), value); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form31t.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form31t.java new file mode 100644 index 000000000..77dc0773f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form31t.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.TargetInsn; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 31t}. See the instruction format spec + * for details. + */ +public final class Form31t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form31t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form31t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof TargetInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + return true; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, regs.get(0).getReg()), offset); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form32x.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form32x.java new file mode 100644 index 000000000..a65361981 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form32x.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.SimpleInsn; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 32x}. See the instruction format spec + * for details. + */ +public final class Form32x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form32x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form32x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return (insn instanceof SimpleInsn) && + (regs.size() == 2) && + unsignedFitsInShort(regs.get(0).getReg()) && + unsignedFitsInShort(regs.get(1).getReg()); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(2); + + bits.set(0, unsignedFitsInShort(regs.get(0).getReg())); + bits.set(1, unsignedFitsInShort(regs.get(1).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + write(out, + opcodeUnit(insn, 0), + (short) regs.get(0).getReg(), + (short) regs.get(1).getReg()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form35c.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form35c.java new file mode 100644 index 000000000..208d4a74f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form35c.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 35c}. See the instruction format spec + * for details. + */ +public final class Form35c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form35c(); + + /** Maximal number of operands */ + private static final int MAX_NUM_OPS = 5; + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form35c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = explicitize(insn.getRegisters()); + return regListString(regs) + ", " + insn.cstString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return insn.cstComment(); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + Constant cst = ci.getConstant(); + if (!((cst instanceof CstMethodRef) || + (cst instanceof CstType))) { + return false; + } + + RegisterSpecList regs = ci.getRegisters(); + return (wordCount(regs) >= 0); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + BitSet bits = new BitSet(sz); + + for (int i = 0; i < sz; i++) { + RegisterSpec reg = regs.get(i); + /* + * The check below adds (category - 1) to the register, to + * account for the fact that the second half of a + * category-2 register has to be represented explicitly in + * the result. + */ + bits.set(i, unsignedFitsInNibble(reg.getReg() + + reg.getCategory() - 1)); + } + + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int cpi = ((CstInsn) insn).getIndex(); + RegisterSpecList regs = explicitize(insn.getRegisters()); + int sz = regs.size(); + int r0 = (sz > 0) ? regs.get(0).getReg() : 0; + int r1 = (sz > 1) ? regs.get(1).getReg() : 0; + int r2 = (sz > 2) ? regs.get(2).getReg() : 0; + int r3 = (sz > 3) ? regs.get(3).getReg() : 0; + int r4 = (sz > 4) ? regs.get(4).getReg() : 0; + + write(out, + opcodeUnit(insn, + makeByte(r4, sz)), // encode the fifth operand here + (short) cpi, + codeUnit(r0, r1, r2, r3)); + } + + /** + * Gets the number of words required for the given register list, where + * category-2 values count as two words. Return {@code -1} if the + * list requires more than five words or contains registers that need + * more than a nibble to identify them. + * + * @param regs {@code non-null;} the register list in question + * @return {@code >= -1;} the number of words required, or {@code -1} + * if the list couldn't possibly fit in this format + */ + private static int wordCount(RegisterSpecList regs) { + int sz = regs.size(); + + if (sz > MAX_NUM_OPS) { + // It can't possibly fit. + return -1; + } + + int result = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = regs.get(i); + result += one.getCategory(); + /* + * The check below adds (category - 1) to the register, to + * account for the fact that the second half of a + * category-2 register has to be represented explicitly in + * the result. + */ + if (!unsignedFitsInNibble(one.getReg() + one.getCategory() - 1)) { + return -1; + } + } + + return (result <= MAX_NUM_OPS) ? result : -1; + } + + /** + * Returns a register list which is equivalent to the given one, + * except that it splits category-2 registers into two explicit + * entries. This returns the original list if no modification is + * required + * + * @param orig {@code non-null;} the original list + * @return {@code non-null;} the list with the described transformation + */ + private static RegisterSpecList explicitize(RegisterSpecList orig) { + int wordCount = wordCount(orig); + int sz = orig.size(); + + if (wordCount == sz) { + return orig; + } + + RegisterSpecList result = new RegisterSpecList(wordCount); + int wordAt = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = orig.get(i); + result.set(wordAt, one); + if (one.getCategory() == 2) { + result.set(wordAt + 1, + RegisterSpec.make(one.getReg() + 1, Type.VOID)); + wordAt += 2; + } else { + wordAt++; + } + } + + result.setImmutable(); + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form3rc.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form3rc.java new file mode 100644 index 000000000..c1b21b675 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form3rc.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 3rc}. See the instruction format spec + * for details. + */ +public final class Form3rc extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form3rc(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form3rc() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return regRangeString(insn.getRegisters()) + ", " + + insn.cstString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return insn.cstComment(); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + Constant cst = ci.getConstant(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + if (!((cst instanceof CstMethodRef) || + (cst instanceof CstType))) { + return false; + } + + RegisterSpecList regs = ci.getRegisters(); + int sz = regs.size(); + + return (regs.size() == 0) || + (isRegListSequential(regs) && + unsignedFitsInShort(regs.get(0).getReg()) && + unsignedFitsInByte(regs.getWordCount())); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + int firstReg = (regs.size() == 0) ? 0 : regs.get(0).getReg(); + int count = regs.getWordCount(); + + write(out, opcodeUnit(insn, count), (short) cpi, (short) firstReg); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form45cc.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form45cc.java new file mode 100644 index 000000000..6875b1b41 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form45cc.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.MultiCstInsn; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstProtoRef; +import com.android.dx.rop.type.Type; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 45cc}. See the instruction format spec + * for details. + */ +public final class Form45cc extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form45cc(); + + /** Maximal number of operands */ + private static final int MAX_NUM_OPS = 5; + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form45cc() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = explicitize(insn.getRegisters()); + return regListString(regs) + ", " + insn.cstString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return insn.cstComment(); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 4; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof MultiCstInsn)) { + return false; + } + + MultiCstInsn mci = (MultiCstInsn) insn; + if (mci.getNumberOfConstants() != 2) { + return false; + } + + int methodIdx = mci.getIndex(0); + int protoIdx = mci.getIndex(1); + if (!unsignedFitsInShort(methodIdx) || !unsignedFitsInShort(protoIdx)) { + return false; + } + + Constant methodRef = mci.getConstant(0); + if (!(methodRef instanceof CstMethodRef)) { + return false; + } + + Constant protoRef = mci.getConstant(1); + if (!(protoRef instanceof CstProtoRef)) { + return false; + } + + RegisterSpecList regs = mci.getRegisters(); + return (wordCount(regs) >= 0); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + BitSet bits = new BitSet(sz); + + for (int i = 0; i < sz; i++) { + RegisterSpec reg = regs.get(i); + /* + * The check below adds (category - 1) to the register, to + * account for the fact that the second half of a + * category-2 register has to be represented explicitly in + * the result. + */ + bits.set(i, unsignedFitsInNibble(reg.getReg() + + reg.getCategory() - 1)); + } + + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + MultiCstInsn mci = (MultiCstInsn) insn; + short regB = (short) mci.getIndex(0); // B is the method index + short regH = (short) mci.getIndex(1); // H is the call site proto index + + RegisterSpecList regs = explicitize(insn.getRegisters()); + int regA = regs.size(); + int regC = (regA > 0) ? regs.get(0).getReg() : 0; + int regD = (regA > 1) ? regs.get(1).getReg() : 0; + int regE = (regA > 2) ? regs.get(2).getReg() : 0; + int regF = (regA > 3) ? regs.get(3).getReg() : 0; + int regG = (regA > 4) ? regs.get(4).getReg() : 0; + + // The output format is: A|G|op BBBB F|E|D|C HHHHH + write(out, + opcodeUnit(insn, makeByte(regG, regA)), + regB, + codeUnit(regC, regD, regE, regF), + regH); + } + + /** + * Gets the number of words required for the given register list, where + * category-2 values count as two words. Return {@code -1} if the + * list requires more than five words or contains registers that need + * more than a nibble to identify them. + * + * @param regs {@code non-null;} the register list in question + * @return {@code >= -1;} the number of words required, or {@code -1} + * if the list couldn't possibly fit in this format + */ + private static int wordCount(RegisterSpecList regs) { + int sz = regs.size(); + + if (sz > MAX_NUM_OPS) { + // It can't possibly fit. + return -1; + } + + int result = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = regs.get(i); + result += one.getCategory(); + /* + * The check below adds (category - 1) to the register, to + * account for the fact that the second half of a + * category-2 register has to be represented explicitly in + * the result. + */ + if (!unsignedFitsInNibble(one.getReg() + one.getCategory() - 1)) { + return -1; + } + } + + return (result <= MAX_NUM_OPS) ? result : -1; + } + + /** + * Returns a register list which is equivalent to the given one, + * except that it splits category-2 registers into two explicit + * entries. This returns the original list if no modification is + * required + * + * @param orig {@code non-null;} the original list + * @return {@code non-null;} the list with the described transformation + */ + private static RegisterSpecList explicitize(RegisterSpecList orig) { + int wordCount = wordCount(orig); + int sz = orig.size(); + + if (wordCount == sz) { + return orig; + } + + RegisterSpecList result = new RegisterSpecList(wordCount); + int wordAt = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = orig.get(i); + result.set(wordAt, one); + if (one.getCategory() == 2) { + result.set(wordAt + 1, + RegisterSpec.make(one.getReg() + 1, Type.VOID)); + wordAt += 2; + } else { + wordAt++; + } + } + + result.setImmutable(); + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form4rcc.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form4rcc.java new file mode 100644 index 000000000..74b57f484 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form4rcc.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.dex.code.MultiCstInsn; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstProtoRef; +import com.android.dx.util.AnnotatedOutput; + +/** + * Instruction format {@code 4rcc}. See the instruction format spec + * for details. + */ +public final class Form4rcc extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form4rcc(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form4rcc() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return regRangeString(insn.getRegisters()) + ", " + + insn.cstString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return insn.cstComment(); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 4; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof MultiCstInsn)) { + return false; + } + + MultiCstInsn mci = (MultiCstInsn) insn; + int methodIdx = mci.getIndex(0); + int protoIdx = mci.getIndex(1); + if (!unsignedFitsInShort(methodIdx) || !unsignedFitsInShort(protoIdx)) { + return false; + } + + Constant methodRef = mci.getConstant(0); + if (!(methodRef instanceof CstMethodRef)) { + return false; + } + + Constant protoRef = mci.getConstant(1); + if (!(protoRef instanceof CstProtoRef)) { + return false; + } + + RegisterSpecList regs = mci.getRegisters(); + int sz = regs.size(); + if (sz == 0) { + return true; + } + + return (unsignedFitsInByte(regs.getWordCount()) && + unsignedFitsInShort(sz) && + unsignedFitsInShort(regs.get(0).getReg()) && + isRegListSequential(regs)); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + MultiCstInsn mci = (MultiCstInsn) insn; + short regB = (short) mci.getIndex(0); // B is the method index + short regH = (short) mci.getIndex(1); // H is the call site proto index + + RegisterSpecList regs = insn.getRegisters(); + short regC = 0; + if (regs.size() > 0) { + regC = (short) regs.get(0).getReg(); + } + int regA = regs.getWordCount(); + + // The output format is: AA|op BBBB CCCC HHHH + write(out, opcodeUnit(insn,regA), regB, regC, regH); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/Form51l.java b/dexlib/src/main/java/com/android/dx/dex/code/form/Form51l.java new file mode 100644 index 000000000..2a31deaef --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/Form51l.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.CstInsn; +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteral64; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.util.AnnotatedOutput; +import java.util.BitSet; + +/** + * Instruction format {@code 51l}. See the instruction format spec + * for details. + */ +public final class Form51l extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form51l(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form51l() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 64); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 5; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + return (cst instanceof CstLiteral64); + } + + /** {@inheritDoc} */ + @Override + public BitSet compatibleRegs(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + BitSet bits = new BitSet(1); + + bits.set(0, unsignedFitsInByte(regs.get(0).getReg())); + return bits; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + long value = + ((CstLiteral64) ((CstInsn) insn).getConstant()).getLongBits(); + + write(out, opcodeUnit(insn, regs.get(0).getReg()), value); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/code/form/SpecialFormat.java b/dexlib/src/main/java/com/android/dx/dex/code/form/SpecialFormat.java new file mode 100644 index 000000000..87091b5a3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/code/form/SpecialFormat.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.code.form; + +import com.android.dx.dex.code.DalvInsn; +import com.android.dx.dex.code.InsnFormat; +import com.android.dx.util.AnnotatedOutput; + +/** + * Instruction format for nonstandard format instructions, which aren't + * generally real instructions but do end up appearing in instruction + * lists. Most of the overridden methods on this class end up throwing + * exceptions, as code should know (implicitly or explicitly) to avoid + * using this class. The one exception is {@link #isCompatible}, which + * always returns {@code true}. + */ +public final class SpecialFormat extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new SpecialFormat(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private SpecialFormat() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + return true; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + throw new RuntimeException("unsupported"); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/AnnotationItem.java b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationItem.java new file mode 100644 index 000000000..2b634a4ae --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationItem.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.annotation.Annotation; +import com.android.dx.rop.annotation.AnnotationVisibility; +import com.android.dx.rop.annotation.NameValuePair; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.ByteArrayAnnotatedOutput; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Single annotation, which consists of a type and a set of name-value + * element pairs. + */ +public final class AnnotationItem extends OffsettedItem { + /** annotation visibility constant: visible at build time only */ + private static final int VISIBILITY_BUILD = 0; + + /** annotation visibility constant: visible at runtime */ + private static final int VISIBILITY_RUNTIME = 1; + + /** annotation visibility constant: visible at runtime only to system */ + private static final int VISIBILITY_SYSTEM = 2; + + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + /** {@code non-null;} unique instance of {@link #TypeIdSorter} */ + private static final TypeIdSorter TYPE_ID_SORTER = new TypeIdSorter(); + + /** {@code non-null;} the annotation to represent */ + private final Annotation annotation; + + /** + * {@code null-ok;} type reference for the annotation type; set during + * {@link #addContents} + */ + private TypeIdItem type; + + /** + * {@code null-ok;} encoded form, ready for writing to a file; set during + * {@link #place0} + */ + private byte[] encodedForm; + + /** + * Comparator that sorts (outer) instances by type id index. + */ + private static class TypeIdSorter implements Comparator { + /** {@inheritDoc} */ + public int compare(AnnotationItem item1, AnnotationItem item2) { + int index1 = item1.type.getIndex(); + int index2 = item2.type.getIndex(); + + if (index1 < index2) { + return -1; + } else if (index1 > index2) { + return 1; + } + + return 0; + } + } + + /** + * Sorts an array of instances, in place, by type id index, + * ignoring all other aspects of the elements. This is only valid + * to use after type id indices are known. + * + * @param array {@code non-null;} array to sort + */ + public static void sortByTypeIdIndex(AnnotationItem[] array) { + Arrays.sort(array, TYPE_ID_SORTER); + } + + /** + * Constructs an instance. + * + * @param annotation {@code non-null;} annotation to represent + * @param dexFile {@code non-null;} dex output + */ + public AnnotationItem(Annotation annotation, DexFile dexFile) { + /* + * The write size isn't known up-front because (the variable-lengthed) + * leb128 type is used to represent some things. + */ + super(ALIGNMENT, -1); + + if (annotation == null) { + throw new NullPointerException("annotation == null"); + } + + this.annotation = annotation; + this.type = null; + this.encodedForm = null; + addContents(dexFile); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATION_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotation.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + AnnotationItem otherAnnotation = (AnnotationItem) other; + + return annotation.compareTo(otherAnnotation.annotation); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return annotation.toHuman(); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + type = file.getTypeIds().intern(annotation.getType()); + ValueEncoder.addContents(file, annotation); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + ValueEncoder encoder = new ValueEncoder(addedTo.getFile(), out); + + encoder.writeAnnotation(annotation, false); + encodedForm = out.toByteArray(); + + // Add one for the visibility byte in front of the encoded annotation. + setWriteSize(encodedForm.length + 1); + } + + /** + * Write a (listing file) annotation for this instance to the given + * output, that consumes no bytes of output. This is for annotating + * a reference to this instance at the point of the reference. + * + * @param out {@code non-null;} where to output to + * @param prefix {@code non-null;} prefix for each line of output + */ + public void annotateTo(AnnotatedOutput out, String prefix) { + out.annotate(0, prefix + "visibility: " + + annotation.getVisibility().toHuman()); + out.annotate(0, prefix + "type: " + annotation.getType().toHuman()); + + for (NameValuePair pair : annotation.getNameValuePairs()) { + CstString name = pair.getName(); + Constant value = pair.getValue(); + + out.annotate(0, prefix + name.toHuman() + ": " + + ValueEncoder.constantToHuman(value)); + } + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + AnnotationVisibility visibility = annotation.getVisibility(); + + if (annotates) { + out.annotate(0, offsetString() + " annotation"); + out.annotate(1, " visibility: VISBILITY_" + visibility); + } + + switch (visibility) { + case BUILD: out.writeByte(VISIBILITY_BUILD); break; + case RUNTIME: out.writeByte(VISIBILITY_RUNTIME); break; + case SYSTEM: out.writeByte(VISIBILITY_SYSTEM); break; + default: { + // EMBEDDED shouldn't appear at the top level. + throw new RuntimeException("shouldn't happen"); + } + } + + if (annotates) { + /* + * The output is to be annotated, so redo the work previously + * done by place0(), except this time annotations will actually + * get emitted. + */ + ValueEncoder encoder = new ValueEncoder(file, out); + encoder.writeAnnotation(annotation, true); + } else { + out.write(encodedForm); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/AnnotationSetItem.java b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationSetItem.java new file mode 100644 index 000000000..048f40ab2 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationSetItem.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.annotation.Annotation; +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +/** + * Set of annotations, where no annotation type appears more than once. + */ +public final class AnnotationSetItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 4; + + /** the size of an entry int the set: one {@code uint} */ + private static final int ENTRY_WRITE_SIZE = 4; + + /** {@code non-null;} the set of annotations */ + private final Annotations annotations; + + /** + * {@code non-null;} set of annotations as individual items in an array. + * Note: The contents have to get sorted by type id before + * writing. + */ + private final AnnotationItem[] items; + + /** + * Constructs an instance. + * + * @param annotations {@code non-null;} set of annotations + * @param dexFile {@code non-null;} dex output + */ + public AnnotationSetItem(Annotations annotations, DexFile dexFile) { + super(ALIGNMENT, writeSize(annotations)); + + this.annotations = annotations; + this.items = new AnnotationItem[annotations.size()]; + + int at = 0; + for (Annotation a : annotations.getAnnotations()) { + items[at] = new AnnotationItem(a, dexFile); + at++; + } + } + + /** + * Gets the write size for the given set. + * + * @param annotations {@code non-null;} the set + * @return {@code > 0;} the write size + */ + private static int writeSize(Annotations annotations) { + // This includes an int size at the start of the list. + + try { + return (annotations.size() * ENTRY_WRITE_SIZE) + 4; + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("list == null"); + } + } + + /** + * Gets the underlying annotations of this instance + * + * @return {@code non-null;} the annotations + */ + public Annotations getAnnotations() { + return annotations; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotations.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + AnnotationSetItem otherSet = (AnnotationSetItem) other; + + return annotations.compareTo(otherSet.annotations); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATION_SET_ITEM; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return annotations.toString(); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection byteData = file.getByteData(); + int size = items.length; + + for (int i = 0; i < size; i++) { + items[i] = byteData.intern(items[i]); + } + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Sort the array to be in type id index order. + AnnotationItem.sortByTypeIdIndex(items); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int size = items.length; + + if (annotates) { + out.annotate(0, offsetString() + " annotation set"); + out.annotate(4, " size: " + Hex.u4(size)); + } + + out.writeInt(size); + + for (int i = 0; i < size; i++) { + AnnotationItem item = items[i]; + int offset = item.getAbsoluteOffset(); + + if (annotates) { + out.annotate(4, " entries[" + Integer.toHexString(i) + "]: " + + Hex.u4(offset)); + items[i].annotateTo(out, " "); + } + + out.writeInt(offset); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/AnnotationSetRefItem.java b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationSetRefItem.java new file mode 100644 index 000000000..53072d8c6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationSetRefItem.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +/** + * Indirect reference to an {@link AnnotationSetItem}. + */ +public final class AnnotationSetRefItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 4; + + /** write size of this class, in bytes */ + private static final int WRITE_SIZE = 4; + + /** {@code non-null;} the annotation set to refer to */ + private AnnotationSetItem annotations; + + /** + * Constructs an instance. + * + * @param annotations {@code non-null;} the annotation set to refer to + */ + public AnnotationSetRefItem(AnnotationSetItem annotations) { + super(ALIGNMENT, WRITE_SIZE); + + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + this.annotations = annotations; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATION_SET_REF_ITEM; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection wordData = file.getWordData(); + + annotations = wordData.intern(annotations); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return annotations.toHuman(); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + int annotationsOff = annotations.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(4, " annotations_off: " + Hex.u4(annotationsOff)); + } + + out.writeInt(annotationsOff); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/AnnotationUtils.java b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationUtils.java new file mode 100644 index 000000000..109583e6f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationUtils.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import static com.android.dx.rop.annotation.AnnotationVisibility.SYSTEM; + +import com.android.dx.rop.annotation.Annotation; +import com.android.dx.rop.annotation.NameValuePair; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstAnnotation; +import com.android.dx.rop.cst.CstArray; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstKnownNull; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; + +import java.util.ArrayList; + +/** + * Utility class for dealing with annotations. + */ +public final class AnnotationUtils { + + /** {@code non-null;} type for {@code AnnotationDefault} annotations */ + private static final CstType ANNOTATION_DEFAULT_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/AnnotationDefault;")); + + /** {@code non-null;} type for {@code EnclosingClass} annotations */ + private static final CstType ENCLOSING_CLASS_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/EnclosingClass;")); + + /** {@code non-null;} type for {@code EnclosingMethod} annotations */ + private static final CstType ENCLOSING_METHOD_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/EnclosingMethod;")); + + /** {@code non-null;} type for {@code InnerClass} annotations */ + private static final CstType INNER_CLASS_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/InnerClass;")); + + /** {@code non-null;} type for {@code MemberClasses} annotations */ + private static final CstType MEMBER_CLASSES_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/MemberClasses;")); + + /** {@code non-null;} type for {@code Signature} annotations */ + private static final CstType SIGNATURE_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/Signature;")); + + /** {@code non-null;} type for {@code SourceDebugExtension} annotations */ + private static final CstType SOURCE_DEBUG_EXTENSION_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/SourceDebugExtension;")); + + /** {@code non-null;} type for {@code Throws} annotations */ + private static final CstType THROWS_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/Throws;")); + + /** {@code non-null;} the UTF-8 constant {@code "accessFlags"} */ + private static final CstString ACCESS_FLAGS_STRING = new CstString("accessFlags"); + + /** {@code non-null;} the UTF-8 constant {@code "name"} */ + private static final CstString NAME_STRING = new CstString("name"); + + /** {@code non-null;} the UTF-8 constant {@code "value"} */ + private static final CstString VALUE_STRING = new CstString("value"); + + /** + * This class is uninstantiable. + */ + private AnnotationUtils() { + // This space intentionally left blank. + } + + /** + * Constructs a standard {@code AnnotationDefault} annotation. + * + * @param defaults {@code non-null;} the defaults, itself as an annotation + * @return {@code non-null;} the constructed annotation + */ + public static Annotation makeAnnotationDefault(Annotation defaults) { + Annotation result = new Annotation(ANNOTATION_DEFAULT_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_STRING, new CstAnnotation(defaults))); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code EnclosingClass} annotation. + * + * @param clazz {@code non-null;} the enclosing class + * @return {@code non-null;} the annotation + */ + public static Annotation makeEnclosingClass(CstType clazz) { + Annotation result = new Annotation(ENCLOSING_CLASS_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_STRING, clazz)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code EnclosingMethod} annotation. + * + * @param method {@code non-null;} the enclosing method + * @return {@code non-null;} the annotation + */ + public static Annotation makeEnclosingMethod(CstMethodRef method) { + Annotation result = new Annotation(ENCLOSING_METHOD_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_STRING, method)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code InnerClass} annotation. + * + * @param name {@code null-ok;} the original name of the class, or + * {@code null} to represent an anonymous class + * @param accessFlags the original access flags + * @return {@code non-null;} the annotation + */ + public static Annotation makeInnerClass(CstString name, int accessFlags) { + Annotation result = new Annotation(INNER_CLASS_TYPE, SYSTEM); + Constant nameCst = (name != null) ? name : CstKnownNull.THE_ONE; + + result.put(new NameValuePair(NAME_STRING, nameCst)); + result.put(new NameValuePair(ACCESS_FLAGS_STRING, + CstInteger.make(accessFlags))); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code MemberClasses} annotation. + * + * @param types {@code non-null;} the list of (the types of) the member classes + * @return {@code non-null;} the annotation + */ + public static Annotation makeMemberClasses(TypeList types) { + CstArray array = makeCstArray(types); + Annotation result = new Annotation(MEMBER_CLASSES_TYPE, SYSTEM); + result.put(new NameValuePair(VALUE_STRING, array)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code Signature} annotation. + * + * @param signature {@code non-null;} the signature string + * @return {@code non-null;} the annotation + */ + public static Annotation makeSignature(CstString signature) { + Annotation result = new Annotation(SIGNATURE_TYPE, SYSTEM); + + /* + * Split the string into pieces that are likely to be common + * across many signatures and the rest of the file. + */ + + String raw = signature.getString(); + int rawLength = raw.length(); + ArrayList pieces = new ArrayList(20); + + for (int at = 0; at < rawLength; /*at*/) { + char c = raw.charAt(at); + int endAt = at + 1; + if (c == 'L') { + // Scan to ';' or '<'. Consume ';' but not '<'. + while (endAt < rawLength) { + c = raw.charAt(endAt); + if (c == ';') { + endAt++; + break; + } else if (c == '<') { + break; + } + endAt++; + } + } else { + // Scan to 'L' without consuming it. + while (endAt < rawLength) { + c = raw.charAt(endAt); + if (c == 'L') { + break; + } + endAt++; + } + } + + pieces.add(raw.substring(at, endAt)); + at = endAt; + } + + int size = pieces.size(); + CstArray.List list = new CstArray.List(size); + + for (int i = 0; i < size; i++) { + list.set(i, new CstString(pieces.get(i))); + } + + list.setImmutable(); + + result.put(new NameValuePair(VALUE_STRING, new CstArray(list))); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code SourceDebugExtension} annotation. + * + * @param smapString {@code non-null;} the SMAP string associated with + * @return {@code non-null;} the annotation + */ + public static Annotation makeSourceDebugExtension(CstString smapString) { + Annotation result = new Annotation(SOURCE_DEBUG_EXTENSION_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_STRING, smapString)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code Throws} annotation. + * + * @param types {@code non-null;} the list of thrown types + * @return {@code non-null;} the annotation + */ + public static Annotation makeThrows(TypeList types) { + CstArray array = makeCstArray(types); + Annotation result = new Annotation(THROWS_TYPE, SYSTEM); + result.put(new NameValuePair(VALUE_STRING, array)); + result.setImmutable(); + return result; + } + + /** + * Converts a {@link TypeList} to a {@link CstArray}. + * + * @param types {@code non-null;} the type list + * @return {@code non-null;} the corresponding array constant + */ + private static CstArray makeCstArray(TypeList types) { + int size = types.size(); + CstArray.List list = new CstArray.List(size); + + for (int i = 0; i < size; i++) { + list.set(i, CstType.intern(types.getType(i))); + } + + list.setImmutable(); + return new CstArray(list); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/AnnotationsDirectoryItem.java b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationsDirectoryItem.java new file mode 100644 index 000000000..21b0bf33b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/AnnotationsDirectoryItem.java @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Per-class directory of annotations. + */ +public final class AnnotationsDirectoryItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 4; + + /** write size of this class's header, in bytes */ + private static final int HEADER_SIZE = 16; + + /** write size of a list element, in bytes */ + private static final int ELEMENT_SIZE = 8; + + /** {@code null-ok;} the class-level annotations, if any */ + private AnnotationSetItem classAnnotations; + + /** {@code null-ok;} the annotated fields, if any */ + private ArrayList fieldAnnotations; + + /** {@code null-ok;} the annotated methods, if any */ + private ArrayList methodAnnotations; + + /** {@code null-ok;} the annotated parameters, if any */ + private ArrayList parameterAnnotations; + + /** + * Constructs an empty instance. + */ + public AnnotationsDirectoryItem() { + super(ALIGNMENT, -1); + + classAnnotations = null; + fieldAnnotations = null; + methodAnnotations = null; + parameterAnnotations = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATIONS_DIRECTORY_ITEM; + } + + /** + * Returns whether this item is empty (has no contents). + * + * @return {@code true} if this item is empty, or {@code false} + * if not + */ + public boolean isEmpty() { + return (classAnnotations == null) && + (fieldAnnotations == null) && + (methodAnnotations == null) && + (parameterAnnotations == null); + } + + /** + * Returns whether this item is a candidate for interning. The only + * interning candidates are ones that only have a non-null + * set of class annotations, with no other lists. + * + * @return {@code true} if this is an interning candidate, or + * {@code false} if not + */ + public boolean isInternable() { + return (classAnnotations != null) && + (fieldAnnotations == null) && + (methodAnnotations == null) && + (parameterAnnotations == null); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + if (classAnnotations == null) { + return 0; + } + + return classAnnotations.hashCode(); + } + + /** + * {@inheritDoc} + * + *

Note:: This throws an exception if this item is not + * internable.

+ * + * @see #isInternable + */ + @Override + public int compareTo0(OffsettedItem other) { + if (! isInternable()) { + throw new UnsupportedOperationException("uninternable instance"); + } + + AnnotationsDirectoryItem otherDirectory = + (AnnotationsDirectoryItem) other; + return classAnnotations.compareTo(otherDirectory.classAnnotations); + } + + /** + * Sets the direct annotations on this instance. These are annotations + * made on the class, per se, as opposed to on one of its members. + * It is only valid to call this method at most once per instance. + * + * @param annotations {@code non-null;} annotations to set for this class + * @param dexFile {@code non-null;} dex output + */ + public void setClassAnnotations(Annotations annotations, DexFile dexFile) { + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + if (classAnnotations != null) { + throw new UnsupportedOperationException( + "class annotations already set"); + } + + classAnnotations = new AnnotationSetItem(annotations, dexFile); + } + + /** + * Adds a field annotations item to this instance. + * + * @param field {@code non-null;} field in question + * @param annotations {@code non-null;} associated annotations to add + * @param dexFile {@code non-null;} dex output + */ + public void addFieldAnnotations(CstFieldRef field, + Annotations annotations, DexFile dexFile) { + if (fieldAnnotations == null) { + fieldAnnotations = new ArrayList(); + } + + fieldAnnotations.add(new FieldAnnotationStruct(field, + new AnnotationSetItem(annotations, dexFile))); + } + + /** + * Adds a method annotations item to this instance. + * + * @param method {@code non-null;} method in question + * @param annotations {@code non-null;} associated annotations to add + * @param dexFile {@code non-null;} dex output + */ + public void addMethodAnnotations(CstMethodRef method, + Annotations annotations, DexFile dexFile) { + if (methodAnnotations == null) { + methodAnnotations = new ArrayList(); + } + + methodAnnotations.add(new MethodAnnotationStruct(method, + new AnnotationSetItem(annotations, dexFile))); + } + + /** + * Adds a parameter annotations item to this instance. + * + * @param method {@code non-null;} method in question + * @param list {@code non-null;} associated list of annotation sets to add + * @param dexFile {@code non-null;} dex output + */ + public void addParameterAnnotations(CstMethodRef method, + AnnotationsList list, DexFile dexFile) { + if (parameterAnnotations == null) { + parameterAnnotations = new ArrayList(); + } + + parameterAnnotations.add(new ParameterAnnotationStruct(method, list, dexFile)); + } + + /** + * Gets the method annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the method annotations, if any + */ + public Annotations getMethodAnnotations(CstMethodRef method) { + if (methodAnnotations == null) { + return null; + } + + for (MethodAnnotationStruct item : methodAnnotations) { + if (item.getMethod().equals(method)) { + return item.getAnnotations(); + } + } + + return null; + } + + /** + * Gets the parameter annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the parameter annotations, if any + */ + public AnnotationsList getParameterAnnotations(CstMethodRef method) { + if (parameterAnnotations == null) { + return null; + } + + for (ParameterAnnotationStruct item : parameterAnnotations) { + if (item.getMethod().equals(method)) { + return item.getAnnotationsList(); + } + } + + return null; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection wordData = file.getWordData(); + + if (classAnnotations != null) { + classAnnotations = wordData.intern(classAnnotations); + } + + if (fieldAnnotations != null) { + for (FieldAnnotationStruct item : fieldAnnotations) { + item.addContents(file); + } + } + + if (methodAnnotations != null) { + for (MethodAnnotationStruct item : methodAnnotations) { + item.addContents(file); + } + } + + if (parameterAnnotations != null) { + for (ParameterAnnotationStruct item : parameterAnnotations) { + item.addContents(file); + } + } + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // We just need to set the write size here. + + int elementCount = listSize(fieldAnnotations) + + listSize(methodAnnotations) + listSize(parameterAnnotations); + setWriteSize(HEADER_SIZE + (elementCount * ELEMENT_SIZE)); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int classOff = OffsettedItem.getAbsoluteOffsetOr0(classAnnotations); + int fieldsSize = listSize(fieldAnnotations); + int methodsSize = listSize(methodAnnotations); + int parametersSize = listSize(parameterAnnotations); + + if (annotates) { + out.annotate(0, offsetString() + " annotations directory"); + out.annotate(4, " class_annotations_off: " + Hex.u4(classOff)); + out.annotate(4, " fields_size: " + + Hex.u4(fieldsSize)); + out.annotate(4, " methods_size: " + + Hex.u4(methodsSize)); + out.annotate(4, " parameters_size: " + + Hex.u4(parametersSize)); + } + + out.writeInt(classOff); + out.writeInt(fieldsSize); + out.writeInt(methodsSize); + out.writeInt(parametersSize); + + if (fieldsSize != 0) { + Collections.sort(fieldAnnotations); + if (annotates) { + out.annotate(0, " fields:"); + } + for (FieldAnnotationStruct item : fieldAnnotations) { + item.writeTo(file, out); + } + } + + if (methodsSize != 0) { + Collections.sort(methodAnnotations); + if (annotates) { + out.annotate(0, " methods:"); + } + for (MethodAnnotationStruct item : methodAnnotations) { + item.writeTo(file, out); + } + } + + if (parametersSize != 0) { + Collections.sort(parameterAnnotations); + if (annotates) { + out.annotate(0, " parameters:"); + } + for (ParameterAnnotationStruct item : parameterAnnotations) { + item.writeTo(file, out); + } + } + } + + /** + * Gets the list size of the given list, or {@code 0} if given + * {@code null}. + * + * @param list {@code null-ok;} the list in question + * @return {@code >= 0;} its size + */ + private static int listSize(ArrayList list) { + if (list == null) { + return 0; + } + + return list.size(); + } + + /** + * Prints out the contents of this instance, in a debugging-friendly + * way. This is meant to be called from {@link ClassDefItem#debugPrint}. + * + * @param out {@code non-null;} where to output to + */ + /*package*/ void debugPrint(PrintWriter out) { + if (classAnnotations != null) { + out.println(" class annotations: " + classAnnotations); + } + + if (fieldAnnotations != null) { + out.println(" field annotations:"); + for (FieldAnnotationStruct item : fieldAnnotations) { + out.println(" " + item.toHuman()); + } + } + + if (methodAnnotations != null) { + out.println(" method annotations:"); + for (MethodAnnotationStruct item : methodAnnotations) { + out.println(" " + item.toHuman()); + } + } + + if (parameterAnnotations != null) { + out.println(" parameter annotations:"); + for (ParameterAnnotationStruct item : parameterAnnotations) { + out.println(" " + item.toHuman()); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/CatchStructs.java b/dexlib/src/main/java/com/android/dx/dex/file/CatchStructs.java new file mode 100644 index 000000000..1e02452cf --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/CatchStructs.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.dex.code.CatchHandlerList; +import com.android.dx.dex.code.CatchTable; +import com.android.dx.dex.code.DalvCode; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.ByteArrayAnnotatedOutput; +import com.android.dx.util.Hex; +import java.io.PrintWriter; +import java.util.Map; +import java.util.TreeMap; + +/** + * List of exception handlers (tuples of covered range, catch type, + * handler address) for a particular piece of code. Instances of this + * class correspond to a {@code try_item[]} and a + * {@code catch_handler_item[]}. + */ +public final class CatchStructs { + /** + * the size of a {@code try_item}: a {@code uint} + * and two {@code ushort}s + */ + private static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2); + + /** {@code non-null;} code that contains the catches */ + private final DalvCode code; + + /** + * {@code null-ok;} the underlying table; set in + * {@link #finishProcessingIfNecessary} + */ + private CatchTable table; + + /** + * {@code null-ok;} the encoded handler list, if calculated; set in + * {@link #encode} + */ + private byte[] encodedHandlers; + + /** + * length of the handlers header (encoded size), if known; used for + * annotation + */ + private int encodedHandlerHeaderSize; + + /** + * {@code null-ok;} map from handler lists to byte offsets, if calculated; set in + * {@link #encode} + */ + private TreeMap handlerOffsets; + + /** + * Constructs an instance. + * + * @param code {@code non-null;} code that contains the catches + */ + public CatchStructs(DalvCode code) { + this.code = code; + this.table = null; + this.encodedHandlers = null; + this.encodedHandlerHeaderSize = 0; + this.handlerOffsets = null; + } + + /** + * Finish processing the catches, if necessary. + */ + private void finishProcessingIfNecessary() { + if (table == null) { + table = code.getCatches(); + } + } + + /** + * Gets the size of the tries list, in entries. + * + * @return {@code >= 0;} the tries list size + */ + public int triesSize() { + finishProcessingIfNecessary(); + return table.size(); + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintWriter out, String prefix) { + annotateEntries(prefix, out, null); + } + + /** + * Encodes the handler lists. + * + * @param file {@code non-null;} file this instance is part of + */ + public void encode(DexFile file) { + finishProcessingIfNecessary(); + + TypeIdsSection typeIds = file.getTypeIds(); + int size = table.size(); + + handlerOffsets = new TreeMap(); + + /* + * First add a map entry for each unique list. The tree structure + * will ensure they are sorted when we reiterate later. + */ + for (int i = 0; i < size; i++) { + handlerOffsets.put(table.get(i).getHandlers(), null); + } + + if (handlerOffsets.size() > 65535) { + throw new UnsupportedOperationException( + "too many catch handlers"); + } + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + + // Write out the handlers "header" consisting of its size in entries. + encodedHandlerHeaderSize = + out.writeUleb128(handlerOffsets.size()); + + // Now write the lists out in order, noting the offset of each. + for (Map.Entry mapping : + handlerOffsets.entrySet()) { + CatchHandlerList list = mapping.getKey(); + int listSize = list.size(); + boolean catchesAll = list.catchesAll(); + + // Set the offset before we do any writing. + mapping.setValue(out.getCursor()); + + if (catchesAll) { + // A size <= 0 means that the list ends with a catch-all. + out.writeSleb128(-(listSize - 1)); + listSize--; + } else { + out.writeSleb128(listSize); + } + + for (int i = 0; i < listSize; i++) { + CatchHandlerList.Entry entry = list.get(i); + out.writeUleb128( + typeIds.indexOf(entry.getExceptionType())); + out.writeUleb128(entry.getHandler()); + } + + if (catchesAll) { + out.writeUleb128(list.get(listSize).getHandler()); + } + } + + encodedHandlers = out.toByteArray(); + } + + /** + * Gets the write size of this instance, in bytes. + * + * @return {@code >= 0;} the write size + */ + public int writeSize() { + return (triesSize() * TRY_ITEM_WRITE_SIZE) + + + encodedHandlers.length; + } + + /** + * Writes this instance to the given stream. + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + */ + public void writeTo(DexFile file, AnnotatedOutput out) { + finishProcessingIfNecessary(); + + if (out.annotates()) { + annotateEntries(" ", null, out); + } + + int tableSize = table.size(); + for (int i = 0; i < tableSize; i++) { + CatchTable.Entry one = table.get(i); + int start = one.getStart(); + int end = one.getEnd(); + int insnCount = end - start; + + if (insnCount >= 65536) { + throw new UnsupportedOperationException( + "bogus exception range: " + Hex.u4(start) + ".." + + Hex.u4(end)); + } + + out.writeInt(start); + out.writeShort(insnCount); + out.writeShort(handlerOffsets.get(one.getHandlers())); + } + + out.write(encodedHandlers); + } + + /** + * Helper method to annotate or simply print the exception handlers. + * Only one of {@code printTo} or {@code annotateTo} should + * be non-null. + * + * @param prefix {@code non-null;} prefix for each line + * @param printTo {@code null-ok;} where to print to + * @param annotateTo {@code null-ok;} where to consume bytes and annotate to + */ + private void annotateEntries(String prefix, PrintWriter printTo, + AnnotatedOutput annotateTo) { + finishProcessingIfNecessary(); + + boolean consume = (annotateTo != null); + int amt1 = consume ? 6 : 0; + int amt2 = consume ? 2 : 0; + int size = table.size(); + String subPrefix = prefix + " "; + + if (consume) { + annotateTo.annotate(0, prefix + "tries:"); + } else { + printTo.println(prefix + "tries:"); + } + + for (int i = 0; i < size; i++) { + CatchTable.Entry entry = table.get(i); + CatchHandlerList handlers = entry.getHandlers(); + String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart()) + + ".." + Hex.u2or4(entry.getEnd()); + String s2 = handlers.toHuman(subPrefix, ""); + + if (consume) { + annotateTo.annotate(amt1, s1); + annotateTo.annotate(amt2, s2); + } else { + printTo.println(s1); + printTo.println(s2); + } + } + + if (! consume) { + // Only emit the handler lists if we are consuming bytes. + return; + } + + annotateTo.annotate(0, prefix + "handlers:"); + annotateTo.annotate(encodedHandlerHeaderSize, + subPrefix + "size: " + Hex.u2(handlerOffsets.size())); + + int lastOffset = 0; + CatchHandlerList lastList = null; + + for (Map.Entry mapping : + handlerOffsets.entrySet()) { + CatchHandlerList list = mapping.getKey(); + int offset = mapping.getValue(); + + if (lastList != null) { + annotateAndConsumeHandlers(lastList, lastOffset, + offset - lastOffset, subPrefix, printTo, annotateTo); + } + + lastList = list; + lastOffset = offset; + } + + annotateAndConsumeHandlers(lastList, lastOffset, + encodedHandlers.length - lastOffset, + subPrefix, printTo, annotateTo); + } + + /** + * Helper for {@link #annotateEntries} to annotate a catch handler list + * while consuming it. + * + * @param handlers {@code non-null;} handlers to annotate + * @param offset {@code >= 0;} the offset of this handler + * @param size {@code >= 1;} the number of bytes the handlers consume + * @param prefix {@code non-null;} prefix for each line + * @param printTo {@code null-ok;} where to print to + * @param annotateTo {@code non-null;} where to annotate to + */ + private static void annotateAndConsumeHandlers(CatchHandlerList handlers, + int offset, int size, String prefix, PrintWriter printTo, + AnnotatedOutput annotateTo) { + String s = handlers.toHuman(prefix, Hex.u2(offset) + ": "); + + if (printTo != null) { + printTo.println(s); + } + + annotateTo.annotate(size, s); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/ClassDataItem.java b/dexlib/src/main/java/com/android/dx/dex/file/ClassDataItem.java new file mode 100644 index 000000000..c3bd0c143 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/ClassDataItem.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstArray; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.cst.Zeroes; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.ByteArrayAnnotatedOutput; +import com.android.dx.util.Writers; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; + +/** + * Representation of all the parts of a Dalvik class that are generally + * "inflated" into an in-memory representation at runtime. Instances of + * this class are represented in a compact streamable form in a + * {@code dex} file, as opposed to a random-access form. + */ +public final class ClassDataItem extends OffsettedItem { + /** {@code non-null;} what class this data is for, just for listing generation */ + private final CstType thisClass; + + /** {@code non-null;} list of static fields */ + private final ArrayList staticFields; + + /** {@code non-null;} list of initial values for static fields */ + private final HashMap staticValues; + + /** {@code non-null;} list of instance fields */ + private final ArrayList instanceFields; + + /** {@code non-null;} list of direct methods */ + private final ArrayList directMethods; + + /** {@code non-null;} list of virtual methods */ + private final ArrayList virtualMethods; + + /** {@code null-ok;} static initializer list; set in {@link #addContents} */ + private CstArray staticValuesConstant; + + /** + * {@code null-ok;} encoded form, ready for writing to a file; set during + * {@link #place0} + */ + private byte[] encodedForm; + + /** + * Constructs an instance. Its sets of members are initially + * empty. + * + * @param thisClass {@code non-null;} what class this data is for, just + * for listing generation + */ + public ClassDataItem(CstType thisClass) { + super(1, -1); + + if (thisClass == null) { + throw new NullPointerException("thisClass == null"); + } + + this.thisClass = thisClass; + this.staticFields = new ArrayList(20); + this.staticValues = new HashMap(40); + this.instanceFields = new ArrayList(20); + this.directMethods = new ArrayList(20); + this.virtualMethods = new ArrayList(20); + this.staticValuesConstant = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CLASS_DATA_ITEM; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return toString(); + } + + /** + * Returns whether this instance is empty. + * + * @return {@code true} if this instance is empty or + * {@code false} if at least one element has been added to it + */ + public boolean isEmpty() { + return staticFields.isEmpty() && instanceFields.isEmpty() + && directMethods.isEmpty() && virtualMethods.isEmpty(); + } + + /** + * Adds a static field. + * + * @param field {@code non-null;} the field to add + * @param value {@code null-ok;} initial value for the field, if any + */ + public void addStaticField(EncodedField field, Constant value) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + if (staticValuesConstant != null) { + throw new UnsupportedOperationException( + "static fields already sorted"); + } + + staticFields.add(field); + staticValues.put(field, value); + } + + /** + * Adds an instance field. + * + * @param field {@code non-null;} the field to add + */ + public void addInstanceField(EncodedField field) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + instanceFields.add(field); + } + + /** + * Adds a direct ({@code static} and/or {@code private}) method. + * + * @param method {@code non-null;} the method to add + */ + public void addDirectMethod(EncodedMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + directMethods.add(method); + } + + /** + * Adds a virtual method. + * + * @param method {@code non-null;} the method to add + */ + public void addVirtualMethod(EncodedMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + virtualMethods.add(method); + } + + /** + * Gets all the methods in this class. The returned list is not linked + * in any way to the underlying lists contained in this instance, but + * the objects contained in the list are shared. + * + * @return {@code non-null;} list of all methods + */ + public ArrayList getMethods() { + int sz = directMethods.size() + virtualMethods.size(); + ArrayList result = new ArrayList(sz); + + result.addAll(directMethods); + result.addAll(virtualMethods); + + return result; + } + + + /** + * Prints out the contents of this instance, in a debugging-friendly + * way. + * + * @param out {@code non-null;} where to output to + * @param verbose whether to be verbose with the output + */ + public void debugPrint(Writer out, boolean verbose) { + PrintWriter pw = Writers.printWriterFor(out); + + int sz = staticFields.size(); + for (int i = 0; i < sz; i++) { + pw.println(" sfields[" + i + "]: " + staticFields.get(i)); + } + + sz = instanceFields.size(); + for (int i = 0; i < sz; i++) { + pw.println(" ifields[" + i + "]: " + instanceFields.get(i)); + } + + sz = directMethods.size(); + for (int i = 0; i < sz; i++) { + pw.println(" dmeths[" + i + "]:"); + directMethods.get(i).debugPrint(pw, verbose); + } + + sz = virtualMethods.size(); + for (int i = 0; i < sz; i++) { + pw.println(" vmeths[" + i + "]:"); + virtualMethods.get(i).debugPrint(pw, verbose); + } + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + if (!staticFields.isEmpty()) { + getStaticValuesConstant(); // Force the fields to be sorted. + for (EncodedField field : staticFields) { + field.addContents(file); + } + } + + if (!instanceFields.isEmpty()) { + Collections.sort(instanceFields); + for (EncodedField field : instanceFields) { + field.addContents(file); + } + } + + if (!directMethods.isEmpty()) { + Collections.sort(directMethods); + for (EncodedMethod method : directMethods) { + method.addContents(file); + } + } + + if (!virtualMethods.isEmpty()) { + Collections.sort(virtualMethods); + for (EncodedMethod method : virtualMethods) { + method.addContents(file); + } + } + } + + /** + * Gets a {@link CstArray} corresponding to {@link #staticValues} if + * it contains any non-zero non-{@code null} values. + * + * @return {@code null-ok;} the corresponding constant or {@code null} if + * there are no values to encode + */ + public CstArray getStaticValuesConstant() { + if ((staticValuesConstant == null) && (staticFields.size() != 0)) { + staticValuesConstant = makeStaticValuesConstant(); + } + + return staticValuesConstant; + } + + /** + * Gets a {@link CstArray} corresponding to {@link #staticValues} if + * it contains any non-zero non-{@code null} values. + * + * @return {@code null-ok;} the corresponding constant or {@code null} if + * there are no values to encode + */ + private CstArray makeStaticValuesConstant() { + // First sort the statics into their final order. + Collections.sort(staticFields); + + /* + * Get the size of staticValues minus any trailing zeros/nulls (both + * nulls per se as well as instances of CstKnownNull). + */ + + int size = staticFields.size(); + while (size > 0) { + EncodedField field = staticFields.get(size - 1); + Constant cst = staticValues.get(field); + if (cst instanceof CstLiteralBits) { + // Note: CstKnownNull extends CstLiteralBits. + if (((CstLiteralBits) cst).getLongBits() != 0) { + break; + } + } else if (cst != null) { + break; + } + size--; + } + + if (size == 0) { + return null; + } + + // There is something worth encoding, so build up a result. + + CstArray.List list = new CstArray.List(size); + for (int i = 0; i < size; i++) { + EncodedField field = staticFields.get(i); + Constant cst = staticValues.get(field); + if (cst == null) { + cst = Zeroes.zeroFor(field.getRef().getType()); + } + list.set(i, cst); + } + list.setImmutable(); + + return new CstArray(list); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + + encodeOutput(addedTo.getFile(), out); + encodedForm = out.toByteArray(); + setWriteSize(encodedForm.length); + } + + /** + * Writes out the encoded form of this instance. + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + */ + private void encodeOutput(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + + if (annotates) { + out.annotate(0, offsetString() + " class data for " + + thisClass.toHuman()); + } + + encodeSize(file, out, "static_fields", staticFields.size()); + encodeSize(file, out, "instance_fields", instanceFields.size()); + encodeSize(file, out, "direct_methods", directMethods.size()); + encodeSize(file, out, "virtual_methods", virtualMethods.size()); + + encodeList(file, out, "static_fields", staticFields); + encodeList(file, out, "instance_fields", instanceFields); + encodeList(file, out, "direct_methods", directMethods); + encodeList(file, out, "virtual_methods", virtualMethods); + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Helper for {@link #encodeOutput}, which writes out the given + * size value, annotating it as well (if annotations are enabled). + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + * @param label {@code non-null;} the label for the purposes of annotation + * @param size {@code >= 0;} the size to write + */ + private static void encodeSize(DexFile file, AnnotatedOutput out, + String label, int size) { + if (out.annotates()) { + out.annotate(String.format(" %-21s %08x", label + "_size:", + size)); + } + + out.writeUleb128(size); + } + + /** + * Helper for {@link #encodeOutput}, which writes out the given + * list. It also annotates the items (if any and if annotations + * are enabled). + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + * @param label {@code non-null;} the label for the purposes of annotation + * @param list {@code non-null;} the list in question + */ + private static void encodeList(DexFile file, AnnotatedOutput out, + String label, ArrayList list) { + int size = list.size(); + int lastIndex = 0; + + if (size == 0) { + return; + } + + if (out.annotates()) { + out.annotate(0, " " + label + ":"); + } + + for (int i = 0; i < size; i++) { + lastIndex = list.get(i).encode(file, out, lastIndex, i); + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + + if (annotates) { + /* + * The output is to be annotated, so redo the work previously + * done by place0(), except this time annotations will actually + * get emitted. + */ + encodeOutput(file, out); + } else { + out.write(encodedForm); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/ClassDefItem.java b/dexlib/src/main/java/com/android/dx/dex/file/ClassDefItem.java new file mode 100644 index 000000000..47f9b69ec --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/ClassDefItem.java @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.SizeOf; +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstArray; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import com.android.dx.util.Writers; + +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; + +/** + * Representation of a Dalvik class, which is basically a set of + * members (fields or methods) along with a few more pieces of + * information. + */ +public final class ClassDefItem extends IndexedItem { + + /** {@code non-null;} type constant for this class */ + private final CstType thisClass; + + /** access flags */ + private final int accessFlags; + + /** + * {@code null-ok;} superclass or {@code null} if this class is a/the + * root class + */ + private final CstType superclass; + + /** {@code null-ok;} list of implemented interfaces */ + private TypeListItem interfaces; + + /** {@code null-ok;} source file name or {@code null} if unknown */ + private final CstString sourceFile; + + /** {@code non-null;} associated class data object */ + private final ClassDataItem classData; + + /** + * {@code null-ok;} item wrapper for the static values, initialized + * in {@link #addContents} + */ + private EncodedArrayItem staticValuesItem; + + /** {@code non-null;} annotations directory */ + private AnnotationsDirectoryItem annotationsDirectory; + + /** + * Constructs an instance. Its sets of members and annotations are + * initially empty. + * + * @param thisClass {@code non-null;} type constant for this class + * @param accessFlags access flags + * @param superclass {@code null-ok;} superclass or {@code null} if + * this class is a/the root class + * @param interfaces {@code non-null;} list of implemented interfaces + * @param sourceFile {@code null-ok;} source file name or + * {@code null} if unknown + */ + public ClassDefItem(CstType thisClass, int accessFlags, + CstType superclass, TypeList interfaces, CstString sourceFile) { + if (thisClass == null) { + throw new NullPointerException("thisClass == null"); + } + + /* + * TODO: Maybe check accessFlags and superclass, at + * least for easily-checked stuff? + */ + + if (interfaces == null) { + throw new NullPointerException("interfaces == null"); + } + + this.thisClass = thisClass; + this.accessFlags = accessFlags; + this.superclass = superclass; + this.interfaces = + (interfaces.size() == 0) ? null : new TypeListItem(interfaces); + this.sourceFile = sourceFile; + this.classData = new ClassDataItem(thisClass); + this.staticValuesItem = null; + this.annotationsDirectory = new AnnotationsDirectoryItem(); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CLASS_DEF_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.CLASS_DEF_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + MixedItemSection byteData = file.getByteData(); + MixedItemSection wordData = file.getWordData(); + MixedItemSection typeLists = file.getTypeLists(); + StringIdsSection stringIds = file.getStringIds(); + + typeIds.intern(thisClass); + + if (!classData.isEmpty()) { + MixedItemSection classDataSection = file.getClassData(); + classDataSection.add(classData); + + CstArray staticValues = classData.getStaticValuesConstant(); + if (staticValues != null) { + staticValuesItem = + byteData.intern(new EncodedArrayItem(staticValues)); + } + } + + if (superclass != null) { + typeIds.intern(superclass); + } + + if (interfaces != null) { + interfaces = typeLists.intern(interfaces); + } + + if (sourceFile != null) { + stringIds.intern(sourceFile); + } + + if (! annotationsDirectory.isEmpty()) { + if (annotationsDirectory.isInternable()) { + annotationsDirectory = wordData.intern(annotationsDirectory); + } else { + wordData.add(annotationsDirectory); + } + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + TypeIdsSection typeIds = file.getTypeIds(); + int classIdx = typeIds.indexOf(thisClass); + int superIdx = (superclass == null) ? -1 : + typeIds.indexOf(superclass); + int interOff = OffsettedItem.getAbsoluteOffsetOr0(interfaces); + int annoOff = annotationsDirectory.isEmpty() ? 0 : + annotationsDirectory.getAbsoluteOffset(); + int sourceFileIdx = (sourceFile == null) ? -1 : + file.getStringIds().indexOf(sourceFile); + int dataOff = classData.isEmpty()? 0 : classData.getAbsoluteOffset(); + int staticValuesOff = + OffsettedItem.getAbsoluteOffsetOr0(staticValuesItem); + + if (annotates) { + out.annotate(0, indexString() + ' ' + thisClass.toHuman()); + out.annotate(4, " class_idx: " + Hex.u4(classIdx)); + out.annotate(4, " access_flags: " + + AccessFlags.classString(accessFlags)); + out.annotate(4, " superclass_idx: " + Hex.u4(superIdx) + + " // " + ((superclass == null) ? "" : + superclass.toHuman())); + out.annotate(4, " interfaces_off: " + Hex.u4(interOff)); + if (interOff != 0) { + TypeList list = interfaces.getList(); + int sz = list.size(); + for (int i = 0; i < sz; i++) { + out.annotate(0, " " + list.getType(i).toHuman()); + } + } + out.annotate(4, " source_file_idx: " + Hex.u4(sourceFileIdx) + + " // " + ((sourceFile == null) ? "" : + sourceFile.toHuman())); + out.annotate(4, " annotations_off: " + Hex.u4(annoOff)); + out.annotate(4, " class_data_off: " + Hex.u4(dataOff)); + out.annotate(4, " static_values_off: " + + Hex.u4(staticValuesOff)); + } + + out.writeInt(classIdx); + out.writeInt(accessFlags); + out.writeInt(superIdx); + out.writeInt(interOff); + out.writeInt(sourceFileIdx); + out.writeInt(annoOff); + out.writeInt(dataOff); + out.writeInt(staticValuesOff); + } + + /** + * Gets the constant corresponding to this class. + * + * @return {@code non-null;} the constant + */ + public CstType getThisClass() { + return thisClass; + } + + /** + * Gets the access flags. + * + * @return the access flags + */ + public int getAccessFlags() { + return accessFlags; + } + + /** + * Gets the superclass. + * + * @return {@code null-ok;} the superclass or {@code null} if + * this class is a/the root class + */ + public CstType getSuperclass() { + return superclass; + } + + /** + * Gets the list of interfaces implemented. + * + * @return {@code non-null;} the interfaces list + */ + public TypeList getInterfaces() { + if (interfaces == null) { + return StdTypeList.EMPTY; + } + + return interfaces.getList(); + } + + /** + * Gets the source file name. + * + * @return {@code null-ok;} the source file name or {@code null} if unknown + */ + public CstString getSourceFile() { + return sourceFile; + } + + /** + * Adds a static field. + * + * @param field {@code non-null;} the field to add + * @param value {@code null-ok;} initial value for the field, if any + */ + public void addStaticField(EncodedField field, Constant value) { + classData.addStaticField(field, value); + } + + /** + * Adds an instance field. + * + * @param field {@code non-null;} the field to add + */ + public void addInstanceField(EncodedField field) { + classData.addInstanceField(field); + } + + /** + * Adds a direct ({@code static} and/or {@code private}) method. + * + * @param method {@code non-null;} the method to add + */ + public void addDirectMethod(EncodedMethod method) { + classData.addDirectMethod(method); + } + + /** + * Adds a virtual method. + * + * @param method {@code non-null;} the method to add + */ + public void addVirtualMethod(EncodedMethod method) { + classData.addVirtualMethod(method); + } + + /** + * Gets all the methods in this class. The returned list is not linked + * in any way to the underlying lists contained in this instance, but + * the objects contained in the list are shared. + * + * @return {@code non-null;} list of all methods + */ + public ArrayList getMethods() { + return classData.getMethods(); + } + + /** + * Sets the direct annotations on this class. These are annotations + * made on the class, per se, as opposed to on one of its members. + * It is only valid to call this method at most once per instance. + * + * @param annotations {@code non-null;} annotations to set for this class + * @param dexFile {@code non-null;} dex output + */ + public void setClassAnnotations(Annotations annotations, DexFile dexFile) { + annotationsDirectory.setClassAnnotations(annotations, dexFile); + } + + /** + * Adds a field annotations item to this class. + * + * @param field {@code non-null;} field in question + * @param annotations {@code non-null;} associated annotations to add + * @param dexFile {@code non-null;} dex output + */ + public void addFieldAnnotations(CstFieldRef field, + Annotations annotations, DexFile dexFile) { + annotationsDirectory.addFieldAnnotations(field, annotations, dexFile); + } + + /** + * Adds a method annotations item to this class. + * + * @param method {@code non-null;} method in question + * @param annotations {@code non-null;} associated annotations to add + * @param dexFile {@code non-null;} dex output + */ + public void addMethodAnnotations(CstMethodRef method, + Annotations annotations, DexFile dexFile) { + annotationsDirectory.addMethodAnnotations(method, annotations, dexFile); + } + + /** + * Adds a parameter annotations item to this class. + * + * @param method {@code non-null;} method in question + * @param list {@code non-null;} associated list of annotation sets to add + * @param dexFile {@code non-null;} dex output + */ + public void addParameterAnnotations(CstMethodRef method, + AnnotationsList list, DexFile dexFile) { + annotationsDirectory.addParameterAnnotations(method, list, dexFile); + } + + /** + * Gets the method annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the method annotations, if any + */ + public Annotations getMethodAnnotations(CstMethodRef method) { + return annotationsDirectory.getMethodAnnotations(method); + } + + /** + * Gets the parameter annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the parameter annotations, if any + */ + public AnnotationsList getParameterAnnotations(CstMethodRef method) { + return annotationsDirectory.getParameterAnnotations(method); + } + + /** + * Prints out the contents of this instance, in a debugging-friendly + * way. + * + * @param out {@code non-null;} where to output to + * @param verbose whether to be verbose with the output + */ + public void debugPrint(Writer out, boolean verbose) { + PrintWriter pw = Writers.printWriterFor(out); + + pw.println(getClass().getName() + " {"); + pw.println(" accessFlags: " + Hex.u2(accessFlags)); + pw.println(" superclass: " + superclass); + pw.println(" interfaces: " + + ((interfaces == null) ? "" : interfaces)); + pw.println(" sourceFile: " + + ((sourceFile == null) ? "" : sourceFile.toQuoted())); + + classData.debugPrint(out, verbose); + annotationsDirectory.debugPrint(pw); + + pw.println("}"); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/ClassDefsSection.java b/dexlib/src/main/java/com/android/dx/dex/file/ClassDefsSection.java new file mode 100644 index 000000000..4db056ba2 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/ClassDefsSection.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.ArrayList; +import java.util.Collection; +import java.util.TreeMap; + +/** + * Class definitions list section of a {@code .dex} file. + */ +public final class ClassDefsSection extends UniformItemSection { + /** + * {@code non-null;} map from type constants for classes to {@link + * ClassDefItem} instances that define those classes + */ + private final TreeMap classDefs; + + /** {@code null-ok;} ordered list of classes; set in {@link #orderItems} */ + private ArrayList orderedDefs; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public ClassDefsSection(DexFile file) { + super("class_defs", file, 4); + + classDefs = new TreeMap(); + orderedDefs = null; + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + if (orderedDefs != null) { + return orderedDefs; + } + + return classDefs.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + Type type = ((CstType) cst).getClassType(); + IndexedItem result = classDefs.get(type); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = classDefs.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "class_defs_size: " + Hex.u4(sz)); + out.annotate(4, "class_defs_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Adds an element to this instance. It is illegal to attempt to add more + * than one class with the same name. + * + * @param clazz {@code non-null;} the class def to add + */ + public void add(ClassDefItem clazz) { + Type type; + + try { + type = clazz.getThisClass().getClassType(); + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("clazz == null"); + } + + throwIfPrepared(); + + if (classDefs.get(type) != null) { + throw new IllegalArgumentException("already added: " + type); + } + + classDefs.put(type, clazz); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int sz = classDefs.size(); + int idx = 0; + + orderedDefs = new ArrayList(sz); + + /* + * Iterate over all the classes, recursively assigning an + * index to each, implicitly skipping the ones that have + * already been assigned by the time this (top-level) + * iteration reaches them. + */ + for (Type type : classDefs.keySet()) { + idx = orderItems0(type, idx, sz - idx); + } + } + + /** + * Helper for {@link #orderItems}, which recursively assigns indices + * to classes. + * + * @param type {@code null-ok;} type ref to assign, if any + * @param idx {@code >= 0;} the next index to assign + * @param maxDepth maximum recursion depth; if negative, this will + * throw an exception indicating class definition circularity + * @return {@code >= 0;} the next index to assign + */ + private int orderItems0(Type type, int idx, int maxDepth) { + ClassDefItem c = classDefs.get(type); + + if ((c == null) || (c.hasIndex())) { + return idx; + } + + if (maxDepth < 0) { + throw new RuntimeException("class circularity with " + type); + } + + maxDepth--; + + CstType superclassCst = c.getSuperclass(); + if (superclassCst != null) { + Type superclass = superclassCst.getClassType(); + idx = orderItems0(superclass, idx, maxDepth); + } + + TypeList interfaces = c.getInterfaces(); + int sz = interfaces.size(); + for (int i = 0; i < sz; i++) { + idx = orderItems0(interfaces.getType(i), idx, maxDepth); + } + + c.setIndex(idx); + orderedDefs.add(c); + return idx + 1; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/CodeItem.java b/dexlib/src/main/java/com/android/dx/dex/file/CodeItem.java new file mode 100644 index 000000000..9e3ae74bf --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/CodeItem.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.dex.code.DalvCode; +import com.android.dx.dex.code.DalvInsnList; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.io.PrintWriter; + +/** + * Representation of all the parts needed for concrete methods in a + * {@code dex} file. + */ +public final class CodeItem extends OffsettedItem { + /** file alignment of this class, in bytes */ + private static final int ALIGNMENT = 4; + + /** write size of the header of this class, in bytes */ + private static final int HEADER_SIZE = 16; + + /** {@code non-null;} method that this code implements */ + private final CstMethodRef ref; + + /** {@code non-null;} the bytecode instructions and associated data */ + private final DalvCode code; + + /** {@code null-ok;} the catches, if needed; set in {@link #addContents} */ + private CatchStructs catches; + + /** whether this instance is for a {@code static} method */ + private final boolean isStatic; + + /** + * {@code non-null;} list of possibly-thrown exceptions; just used in + * generating debugging output (listings) + */ + private final TypeList throwsList; + + /** + * {@code null-ok;} the debug info or {@code null} if there is none; + * set in {@link #addContents} + */ + private DebugInfoItem debugInfo; + + /** + * Constructs an instance. + * + * @param ref {@code non-null;} method that this code implements + * @param code {@code non-null;} the underlying code + * @param isStatic whether this instance is for a {@code static} + * method + * @param throwsList {@code non-null;} list of possibly-thrown exceptions, + * just used in generating debugging output (listings) + */ + public CodeItem(CstMethodRef ref, DalvCode code, boolean isStatic, + TypeList throwsList) { + super(ALIGNMENT, -1); + + if (ref == null) { + throw new NullPointerException("ref == null"); + } + + if (code == null) { + throw new NullPointerException("code == null"); + } + + if (throwsList == null) { + throw new NullPointerException("throwsList == null"); + } + + this.ref = ref; + this.code = code; + this.isStatic = isStatic; + this.throwsList = throwsList; + this.catches = null; + this.debugInfo = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CODE_ITEM; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection byteData = file.getByteData(); + TypeIdsSection typeIds = file.getTypeIds(); + + if (code.hasPositions() || code.hasLocals()) { + debugInfo = new DebugInfoItem(code, isStatic, ref); + byteData.add(debugInfo); + } + + if (code.hasAnyCatches()) { + for (Type type : code.getCatchTypes()) { + typeIds.intern(type); + } + catches = new CatchStructs(code); + } + + for (Constant c : code.getInsnConstants()) { + file.internIfAppropriate(c); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "CodeItem{" + toHuman() + "}"; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return ref.toHuman(); + } + + /** + * Gets the reference to the method this instance implements. + * + * @return {@code non-null;} the method reference + */ + public CstMethodRef getRef() { + return ref; + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} per-line prefix to use + * @param verbose whether to be verbose with the output + */ + public void debugPrint(PrintWriter out, String prefix, boolean verbose) { + out.println(ref.toHuman() + ":"); + + DalvInsnList insns = code.getInsns(); + out.println("regs: " + Hex.u2(getRegistersSize()) + + "; ins: " + Hex.u2(getInsSize()) + "; outs: " + + Hex.u2(getOutsSize())); + + insns.debugPrint(out, prefix, verbose); + + String prefix2 = prefix + " "; + + if (catches != null) { + out.print(prefix); + out.println("catches"); + catches.debugPrint(out, prefix2); + } + + if (debugInfo != null) { + out.print(prefix); + out.println("debug info"); + debugInfo.debugPrint(out, prefix2); + } + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + final DexFile file = addedTo.getFile(); + int catchesSize; + + /* + * In order to get the catches and insns, all the code's + * constants need to be assigned indices. + */ + code.assignIndices(new DalvCode.AssignIndicesCallback() { + public int getIndex(Constant cst) { + IndexedItem item = file.findItemOrNull(cst); + if (item == null) { + return -1; + } + return item.getIndex(); + } + }); + + if (catches != null) { + catches.encode(file); + catchesSize = catches.writeSize(); + } else { + catchesSize = 0; + } + + /* + * The write size includes the header, two bytes per code + * unit, post-code padding if necessary, and however much + * space the catches need. + */ + + int insnsSize = code.getInsns().codeSize(); + if ((insnsSize & 1) != 0) { + insnsSize++; + } + + setWriteSize(HEADER_SIZE + (insnsSize * 2) + catchesSize); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int regSz = getRegistersSize(); + int outsSz = getOutsSize(); + int insSz = getInsSize(); + int insnsSz = code.getInsns().codeSize(); + boolean needPadding = (insnsSz & 1) != 0; + int triesSz = (catches == null) ? 0 : catches.triesSize(); + int debugOff = (debugInfo == null) ? 0 : debugInfo.getAbsoluteOffset(); + + if (annotates) { + out.annotate(0, offsetString() + ' ' + ref.toHuman()); + out.annotate(2, " registers_size: " + Hex.u2(regSz)); + out.annotate(2, " ins_size: " + Hex.u2(insSz)); + out.annotate(2, " outs_size: " + Hex.u2(outsSz)); + out.annotate(2, " tries_size: " + Hex.u2(triesSz)); + out.annotate(4, " debug_off: " + Hex.u4(debugOff)); + out.annotate(4, " insns_size: " + Hex.u4(insnsSz)); + + // This isn't represented directly here, but it is useful to see. + int size = throwsList.size(); + if (size != 0) { + out.annotate(0, " throws " + StdTypeList.toHuman(throwsList)); + } + } + + out.writeShort(regSz); + out.writeShort(insSz); + out.writeShort(outsSz); + out.writeShort(triesSz); + out.writeInt(debugOff); + out.writeInt(insnsSz); + + writeCodes(file, out); + + if (catches != null) { + if (needPadding) { + if (annotates) { + out.annotate(2, " padding: 0"); + } + out.writeShort(0); + } + + catches.writeTo(file, out); + } + + if (annotates) { + /* + * These are pointed at in the code header (above), but it's less + * distracting to expand on them at the bottom of the code. + */ + if (debugInfo != null) { + out.annotate(0, " debug info"); + debugInfo.annotateTo(file, out, " "); + } + } + } + + /** + * Helper for {@link #writeTo0} which writes out the actual bytecode. + * + * @param file {@code non-null;} file we are part of + * @param out {@code non-null;} where to write to + */ + private void writeCodes(DexFile file, AnnotatedOutput out) { + DalvInsnList insns = code.getInsns(); + + try { + insns.writeTo(out); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, "...while writing " + + "instructions for " + ref.toHuman()); + } + } + + /** + * Get the in registers count. + * + * @return the count + */ + private int getInsSize() { + return ref.getParameterWordCount(isStatic); + } + + /** + * Get the out registers count. + * + * @return the count + */ + private int getOutsSize() { + return code.getInsns().getOutsSize(); + } + + /** + * Get the total registers count. + * + * @return the count + */ + private int getRegistersSize() { + return code.getInsns().getRegistersSize(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoConstants.java b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoConstants.java new file mode 100644 index 000000000..78b6b049d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoConstants.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +/** + * Constants for the dex debug info state machine format. + */ +public interface DebugInfoConstants { + + /* + * normal opcodes + */ + + /** + * Terminates a debug info sequence for a method.

+ * Args: none + * + */ + static final int DBG_END_SEQUENCE = 0x00; + + /** + * Advances the program counter/address register without emitting + * a positions entry.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — amount to advance pc by + *
+ */ + static final int DBG_ADVANCE_PC = 0x01; + + /** + * Advances the line register without emitting + * a positions entry.

+ * + * Args: + *

    + *
  1. Signed LEB128 — amount to change line register by. + *
+ */ + static final int DBG_ADVANCE_LINE = 0x02; + + /** + * Introduces a local variable at the current address.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — register that will contain local. + *
  2. Unsigned LEB128 — string index (shifted by 1) of local name. + *
  3. Unsigned LEB128 — type index (shifted by 1) of type. + *
+ */ + static final int DBG_START_LOCAL = 0x03; + + /** + * Introduces a local variable at the current address with a type + * signature specified.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — register that will contain local. + *
  2. Unsigned LEB128 — string index (shifted by 1) of local name. + *
  3. Unsigned LEB128 — type index (shifted by 1) of type. + *
  4. Unsigned LEB128 — string index (shifted by 1) of + * type signature. + *
+ */ + static final int DBG_START_LOCAL_EXTENDED = 0x04; + + /** + * Marks a currently-live local variable as out of scope at the + * current address.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — register that contained local + *
+ */ + static final int DBG_END_LOCAL = 0x05; + + /** + * Re-introduces a local variable at the current address. The name + * and type are the same as the last local that was live in the specified + * register.

+ * + * Args: + *

    + *
  1. Unsigned LEB128 — register to re-start. + *
+ */ + static final int DBG_RESTART_LOCAL = 0x06; + + + /** + * Sets the "prologue_end" state machine register, indicating that the + * next position entry that is added should be considered the end of + * a method prologue (an appropriate place for a method breakpoint).

+ * + * The prologue_end register is cleared by any special + * ({@code >= OPCODE_BASE}) opcode. + */ + static final int DBG_SET_PROLOGUE_END = 0x07; + + /** + * Sets the "epilogue_begin" state machine register, indicating that the + * next position entry that is added should be considered the beginning of + * a method epilogue (an appropriate place to suspend execution before + * method exit).

+ * + * The epilogue_begin register is cleared by any special + * ({@code >= OPCODE_BASE}) opcode. + */ + static final int DBG_SET_EPILOGUE_BEGIN = 0x08; + + /** + * Sets the current file that that line numbers refer to. All subsequent + * line number entries make reference to this source file name, instead + * of the default name specified in code_item. + * + * Args: + *

    + *
  1. Unsigned LEB128 — string index (shifted by 1) of source + * file name. + *
+ */ + static final int DBG_SET_FILE = 0x09; + + /* IF YOU ADD A NEW OPCODE, increase OPCODE_BASE */ + + /* + * "special opcode" configuration, essentially what's found in + * the line number program header in DWARFv3, Section 6.2.4 + */ + + /** the smallest value a special opcode can take */ + static final int DBG_FIRST_SPECIAL = 0x0a; + static final int DBG_LINE_BASE = -4; + static final int DBG_LINE_RANGE = 15; + // MIN_INSN_LENGTH is always 1 +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoDecoder.java b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoDecoder.java new file mode 100644 index 000000000..203b0f856 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoDecoder.java @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.util.ByteArrayByteInput; +import com.android.dex.util.ByteInput; +import com.android.dex.util.ExceptionWithContext; +import com.android.dex.Leb128; +import com.android.dx.dex.code.DalvCode; +import com.android.dx.dex.code.DalvInsnList; +import com.android.dx.dex.code.LocalList; +import com.android.dx.dex.code.PositionList; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_LINE; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_PC; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_LOCAL; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_SEQUENCE; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_FIRST_SPECIAL; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_BASE; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_RANGE; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_RESTART_LOCAL; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_EPILOGUE_BEGIN; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_FILE; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_PROLOGUE_END; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL_EXTENDED; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * A decoder for the dex debug info state machine format. + * This code exists mostly as a reference implementation and test for + * for the {@code DebugInfoEncoder} + */ +public class DebugInfoDecoder { + /** encoded debug info */ + private final byte[] encoded; + + /** positions decoded */ + private final ArrayList positions; + + /** locals decoded */ + private final ArrayList locals; + + /** size of code block in code units */ + private final int codesize; + + /** indexed by register, the last local variable live in a reg */ + private final LocalEntry[] lastEntryForReg; + + /** method descriptor of method this debug info is for */ + private final Prototype desc; + + /** true if method is static */ + private final boolean isStatic; + + /** dex file this debug info will be stored in */ + private final DexFile file; + + /** + * register size, in register units, of the register space + * used by this method + */ + private final int regSize; + + /** current decoding state: line number */ + private int line = 1; + + /** current decoding state: bytecode address */ + private int address = 0; + + /** string index of the string "this" */ + private final int thisStringIdx; + + /** + * Constructs an instance. + * + * @param encoded encoded debug info + * @param codesize size of code block in code units + * @param regSize register size, in register units, of the register space + * used by this method + * @param isStatic true if method is static + * @param ref method descriptor of method this debug info is for + * @param file dex file this debug info will be stored in + */ + DebugInfoDecoder(byte[] encoded, int codesize, int regSize, + boolean isStatic, CstMethodRef ref, DexFile file) { + if (encoded == null) { + throw new NullPointerException("encoded == null"); + } + + this.encoded = encoded; + this.isStatic = isStatic; + this.desc = ref.getPrototype(); + this.file = file; + this.regSize = regSize; + + positions = new ArrayList(); + locals = new ArrayList(); + this.codesize = codesize; + lastEntryForReg = new LocalEntry[regSize]; + + int idx = -1; + + try { + idx = file.getStringIds().indexOf(new CstString("this")); + } catch (IllegalArgumentException ex) { + /* + * Silently tolerate not finding "this". It just means that + * no method has local variable info that looks like + * a standard instance method. + */ + } + + thisStringIdx = idx; + } + + /** + * An entry in the resulting postions table + */ + static private class PositionEntry { + /** bytecode address */ + public int address; + + /** line number */ + public int line; + + public PositionEntry(int address, int line) { + this.address = address; + this.line = line; + } + } + + /** + * An entry in the resulting locals table + */ + static private class LocalEntry { + /** address of event */ + public int address; + + /** {@code true} iff it's a local start */ + public boolean isStart; + + /** register number */ + public int reg; + + /** index of name in strings table */ + public int nameIndex; + + /** index of type in types table */ + public int typeIndex; + + /** index of type signature in strings table */ + public int signatureIndex; + + public LocalEntry(int address, boolean isStart, int reg, int nameIndex, + int typeIndex, int signatureIndex) { + this.address = address; + this.isStart = isStart; + this.reg = reg; + this.nameIndex = nameIndex; + this.typeIndex = typeIndex; + this.signatureIndex = signatureIndex; + } + + public String toString() { + return String.format("[%x %s v%d %04x %04x %04x]", + address, isStart ? "start" : "end", reg, + nameIndex, typeIndex, signatureIndex); + } + } + + /** + * Gets the decoded positions list. + * Valid after calling {@code decode}. + * + * @return positions list in ascending address order. + */ + public List getPositionList() { + return positions; + } + + /** + * Gets the decoded locals list, in ascending start-address order. + * Valid after calling {@code decode}. + * + * @return locals list in ascending address order. + */ + public List getLocals() { + return locals; + } + + /** + * Decodes the debug info sequence. + */ + public void decode() { + try { + decode0(); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, + "...while decoding debug info"); + } + } + + /** + * Reads a string index. String indicies are offset by 1, and a 0 value + * in the stream (-1 as returned by this method) means "null" + * + * @return index into file's string ids table, -1 means null + * @throws IOException + */ + private int readStringIndex(ByteInput bs) throws IOException { + int offsetIndex = Leb128.readUnsignedLeb128(bs); + + return offsetIndex - 1; + } + + /** + * Gets the register that begins the method's parameter range (including + * the 'this' parameter for non-static methods). The range continues until + * {@code regSize} + * + * @return register as noted above. + */ + private int getParamBase() { + return regSize + - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1); + } + + private void decode0() throws IOException { + ByteInput bs = new ByteArrayByteInput(encoded); + + line = Leb128.readUnsignedLeb128(bs); + int szParams = Leb128.readUnsignedLeb128(bs); + StdTypeList params = desc.getParameterTypes(); + int curReg = getParamBase(); + + if (szParams != params.size()) { + throw new RuntimeException( + "Mismatch between parameters_size and prototype"); + } + + if (!isStatic) { + // Start off with implicit 'this' entry + LocalEntry thisEntry = + new LocalEntry(0, true, curReg, thisStringIdx, 0, 0); + locals.add(thisEntry); + lastEntryForReg[curReg] = thisEntry; + curReg++; + } + + for (int i = 0; i < szParams; i++) { + Type paramType = params.getType(i); + LocalEntry le; + + int nameIdx = readStringIndex(bs); + + if (nameIdx == -1) { + /* + * Unnamed parameter; often but not always filled in by an + * extended start op after the prologue + */ + le = new LocalEntry(0, true, curReg, -1, 0, 0); + } else { + // TODO: Final 0 should be idx of paramType.getDescriptor(). + le = new LocalEntry(0, true, curReg, nameIdx, 0, 0); + } + + locals.add(le); + lastEntryForReg[curReg] = le; + curReg += paramType.getCategory(); + } + + for (;;) { + int opcode = bs.readByte() & 0xff; + + switch (opcode) { + case DBG_START_LOCAL: { + int reg = Leb128.readUnsignedLeb128(bs); + int nameIdx = readStringIndex(bs); + int typeIdx = readStringIndex(bs); + LocalEntry le = new LocalEntry( + address, true, reg, nameIdx, typeIdx, 0); + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_START_LOCAL_EXTENDED: { + int reg = Leb128.readUnsignedLeb128(bs); + int nameIdx = readStringIndex(bs); + int typeIdx = readStringIndex(bs); + int sigIdx = readStringIndex(bs); + LocalEntry le = new LocalEntry( + address, true, reg, nameIdx, typeIdx, sigIdx); + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_RESTART_LOCAL: { + int reg = Leb128.readUnsignedLeb128(bs); + LocalEntry prevle; + LocalEntry le; + + try { + prevle = lastEntryForReg[reg]; + + if (prevle.isStart) { + throw new RuntimeException("nonsensical " + + "RESTART_LOCAL on live register v" + + reg); + } + + le = new LocalEntry(address, true, reg, + prevle.nameIndex, prevle.typeIndex, 0); + } catch (NullPointerException ex) { + throw new RuntimeException( + "Encountered RESTART_LOCAL on new v" + reg); + } + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_END_LOCAL: { + int reg = Leb128.readUnsignedLeb128(bs); + LocalEntry prevle; + LocalEntry le; + + try { + prevle = lastEntryForReg[reg]; + + if (!prevle.isStart) { + throw new RuntimeException("nonsensical " + + "END_LOCAL on dead register v" + reg); + } + + le = new LocalEntry(address, false, reg, + prevle.nameIndex, prevle.typeIndex, + prevle.signatureIndex); + } catch (NullPointerException ex) { + throw new RuntimeException( + "Encountered END_LOCAL on new v" + reg); + } + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_END_SEQUENCE: + // all done + return; + + case DBG_ADVANCE_PC: + address += Leb128.readUnsignedLeb128(bs); + break; + + case DBG_ADVANCE_LINE: + line += Leb128.readSignedLeb128(bs); + break; + + case DBG_SET_PROLOGUE_END: + //TODO do something with this. + break; + + case DBG_SET_EPILOGUE_BEGIN: + //TODO do something with this. + break; + + case DBG_SET_FILE: + //TODO do something with this. + break; + + default: + if (opcode < DBG_FIRST_SPECIAL) { + throw new RuntimeException( + "Invalid extended opcode encountered " + + opcode); + } + + int adjopcode = opcode - DBG_FIRST_SPECIAL; + + address += adjopcode / DBG_LINE_RANGE; + line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE); + + positions.add(new PositionEntry(address, line)); + break; + + } + } + } + + /** + * Validates an encoded debug info stream against data used to encode it, + * throwing an exception if they do not match. Used to validate the + * encoder. + * + * @param info encoded debug info + * @param file {@code non-null;} file to refer to during decoding + * @param ref {@code non-null;} method whose info is being decoded + * @param code {@code non-null;} original code object that was encoded + * @param isStatic whether the method is static + */ + public static void validateEncode(byte[] info, DexFile file, + CstMethodRef ref, DalvCode code, boolean isStatic) { + PositionList pl = code.getPositions(); + LocalList ll = code.getLocals(); + DalvInsnList insns = code.getInsns(); + int codeSize = insns.codeSize(); + int countRegisters = insns.getRegistersSize(); + + try { + validateEncode0(info, codeSize, countRegisters, + isStatic, ref, file, pl, ll); + } catch (RuntimeException ex) { + System.err.println("instructions:"); + insns.debugPrint(System.err, " ", true); + System.err.println("local list:"); + ll.debugPrint(System.err, " "); + throw ExceptionWithContext.withContext(ex, + "while processing " + ref.toHuman()); + } + } + + private static void validateEncode0(byte[] info, int codeSize, + int countRegisters, boolean isStatic, CstMethodRef ref, + DexFile file, PositionList pl, LocalList ll) { + DebugInfoDecoder decoder + = new DebugInfoDecoder(info, codeSize, countRegisters, + isStatic, ref, file); + + decoder.decode(); + + /* + * Go through the decoded position entries, matching up + * with original entries. + */ + + List decodedEntries = decoder.getPositionList(); + + if (decodedEntries.size() != pl.size()) { + throw new RuntimeException( + "Decoded positions table not same size was " + + decodedEntries.size() + " expected " + pl.size()); + } + + for (PositionEntry entry : decodedEntries) { + boolean found = false; + for (int i = pl.size() - 1; i >= 0; i--) { + PositionList.Entry ple = pl.get(i); + + if (entry.line == ple.getPosition().getLine() + && entry.address == ple.getAddress()) { + found = true; + break; + } + } + + if (!found) { + throw new RuntimeException ("Could not match position entry: " + + entry.address + ", " + entry.line); + } + } + + /* + * Go through the original local list, in order, matching up + * with decoded entries. + */ + + List decodedLocals = decoder.getLocals(); + int thisStringIdx = decoder.thisStringIdx; + int decodedSz = decodedLocals.size(); + int paramBase = decoder.getParamBase(); + + /* + * Preflight to fill in any parameters that were skipped in + * the prologue (including an implied "this") but then + * identified by full signature. + */ + for (int i = 0; i < decodedSz; i++) { + LocalEntry entry = decodedLocals.get(i); + int idx = entry.nameIndex; + + if ((idx < 0) || (idx == thisStringIdx)) { + for (int j = i + 1; j < decodedSz; j++) { + LocalEntry e2 = decodedLocals.get(j); + if (e2.address != 0) { + break; + } + if ((entry.reg == e2.reg) && e2.isStart) { + decodedLocals.set(i, e2); + decodedLocals.remove(j); + decodedSz--; + break; + } + } + } + } + + int origSz = ll.size(); + int decodeAt = 0; + boolean problem = false; + + for (int i = 0; i < origSz; i++) { + LocalList.Entry origEntry = ll.get(i); + + if (origEntry.getDisposition() + == LocalList.Disposition.END_REPLACED) { + /* + * The encoded list doesn't represent replacements, so + * ignore them for the sake of comparison. + */ + continue; + } + + LocalEntry decodedEntry; + + do { + decodedEntry = decodedLocals.get(decodeAt); + if (decodedEntry.nameIndex >= 0) { + break; + } + /* + * A negative name index means this is an anonymous + * parameter, and we shouldn't expect to see it in the + * original list. So, skip it. + */ + decodeAt++; + } while (decodeAt < decodedSz); + + int decodedAddress = decodedEntry.address; + + if (decodedEntry.reg != origEntry.getRegister()) { + System.err.println("local register mismatch at orig " + i + + " / decoded " + decodeAt); + problem = true; + break; + } + + if (decodedEntry.isStart != origEntry.isStart()) { + System.err.println("local start/end mismatch at orig " + i + + " / decoded " + decodeAt); + problem = true; + break; + } + + /* + * The secondary check here accounts for the fact that a + * parameter might not be marked as starting at 0 in the + * original list. + */ + if ((decodedAddress != origEntry.getAddress()) + && !((decodedAddress == 0) + && (decodedEntry.reg >= paramBase))) { + System.err.println("local address mismatch at orig " + i + + " / decoded " + decodeAt); + problem = true; + break; + } + + decodeAt++; + } + + if (problem) { + System.err.println("decoded locals:"); + for (LocalEntry e : decodedLocals) { + System.err.println(" " + e); + } + throw new RuntimeException("local table problem"); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoEncoder.java b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoEncoder.java new file mode 100644 index 000000000..9f57b6b64 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoEncoder.java @@ -0,0 +1,928 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.dex.code.LocalList; +import com.android.dx.dex.code.PositionList; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_LINE; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_PC; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_LOCAL; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_SEQUENCE; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_FIRST_SPECIAL; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_BASE; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_RANGE; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_RESTART_LOCAL; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_PROLOGUE_END; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL; +import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL_EXTENDED; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.ByteArrayAnnotatedOutput; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; + +/** + * An encoder for the dex debug info state machine format. The format + * for each method enrty is as follows: + *
    + *
  1. signed LEB128: initial value for line register. + *
  2. n instances of signed LEB128: string indicies (offset by 1) + * for each method argument in left-to-right order + * with {@code this} excluded. A value of '0' indicates "no name" + *
  3. A sequence of special or normal opcodes as defined in + * {@code DebugInfoConstants}. + *
  4. A single terminating {@code OP_END_SEQUENCE} + *
+ */ +public final class DebugInfoEncoder { + private static final boolean DEBUG = false; + + /** {@code null-ok;} positions (line numbers) to encode */ + private final PositionList positions; + + /** {@code null-ok;} local variables to encode */ + private final LocalList locals; + + private final ByteArrayAnnotatedOutput output; + private final DexFile file; + private final int codeSize; + private final int regSize; + + private final Prototype desc; + private final boolean isStatic; + + /** current encoding state: bytecode address */ + private int address = 0; + + /** current encoding state: line number */ + private int line = 1; + + /** + * if non-null: the output to write annotations to. No normal + * output is written to this. + */ + private AnnotatedOutput annotateTo; + + /** if non-null: another possible output for annotations */ + private PrintWriter debugPrint; + + /** if non-null: the prefix for each annotation or debugPrint line */ + private String prefix; + + /** true if output should be consumed during annotation */ + private boolean shouldConsume; + + /** indexed by register; last local alive in register */ + private final LocalList.Entry[] lastEntryForReg; + + /** + * Creates an instance. + * + * @param positions {@code null-ok;} positions (line numbers) to encode + * @param locals {@code null-ok;} local variables to encode + * @param file {@code null-ok;} may only be {@code null} if simply using + * this class to do a debug print + * @param codeSize + * @param regSize + * @param isStatic + * @param ref + */ + public DebugInfoEncoder(PositionList positions, LocalList locals, + DexFile file, int codeSize, int regSize, + boolean isStatic, CstMethodRef ref) { + this.positions = positions; + this.locals = locals; + this.file = file; + this.desc = ref.getPrototype(); + this.isStatic = isStatic; + this.codeSize = codeSize; + this.regSize = regSize; + + output = new ByteArrayAnnotatedOutput(); + lastEntryForReg = new LocalList.Entry[regSize]; + } + + /** + * Annotates or writes a message to the {@code debugPrint} writer + * if applicable. + * + * @param length the number of bytes associated with this message + * @param message the message itself + */ + private void annotate(int length, String message) { + if (prefix != null) { + message = prefix + message; + } + + if (annotateTo != null) { + annotateTo.annotate(shouldConsume ? length : 0, message); + } + + if (debugPrint != null) { + debugPrint.println(message); + } + } + + /** + * Converts this (PositionList, LocalList) pair into a state machine + * sequence. + * + * @return {@code non-null;} encoded byte sequence without padding and + * terminated with a {@code 0x00} byte + */ + public byte[] convert() { + try { + byte[] ret; + ret = convert0(); + + if (DEBUG) { + for (int i = 0 ; i < ret.length; i++) { + System.err.printf("byte %02x\n", (0xff & ret[i])); + } + } + + return ret; + } catch (IOException ex) { + throw ExceptionWithContext + .withContext(ex, "...while encoding debug info"); + } + } + + /** + * Converts and produces annotations on a stream. Does not write + * actual bits to the {@code AnnotatedOutput}. + * + * @param prefix {@code null-ok;} prefix to attach to each line of output + * @param debugPrint {@code null-ok;} if specified, an alternate output for + * annotations + * @param out {@code null-ok;} if specified, where annotations should go + * @param consume whether to claim to have consumed output for + * {@code out} + * @return {@code non-null;} encoded output + */ + public byte[] convertAndAnnotate(String prefix, PrintWriter debugPrint, + AnnotatedOutput out, boolean consume) { + this.prefix = prefix; + this.debugPrint = debugPrint; + annotateTo = out; + shouldConsume = consume; + + byte[] result = convert(); + + return result; + } + + private byte[] convert0() throws IOException { + ArrayList sortedPositions = buildSortedPositions(); + ArrayList methodArgs = extractMethodArguments(); + + emitHeader(sortedPositions, methodArgs); + + // TODO: Make this mark be the actual prologue end. + output.writeByte(DBG_SET_PROLOGUE_END); + + if (annotateTo != null || debugPrint != null) { + annotate(1, String.format("%04x: prologue end",address)); + } + + int positionsSz = sortedPositions.size(); + int localsSz = locals.size(); + + // Current index in sortedPositions + int curPositionIdx = 0; + // Current index in locals + int curLocalIdx = 0; + + for (;;) { + /* + * Emit any information for the current address. + */ + + curLocalIdx = emitLocalsAtAddress(curLocalIdx); + curPositionIdx = + emitPositionsAtAddress(curPositionIdx, sortedPositions); + + /* + * Figure out what the next important address is. + */ + + int nextAddrL = Integer.MAX_VALUE; // local variable + int nextAddrP = Integer.MAX_VALUE; // position (line number) + + if (curLocalIdx < localsSz) { + nextAddrL = locals.get(curLocalIdx).getAddress(); + } + + if (curPositionIdx < positionsSz) { + nextAddrP = sortedPositions.get(curPositionIdx).getAddress(); + } + + int next = Math.min(nextAddrP, nextAddrL); + + // No next important address == done. + if (next == Integer.MAX_VALUE) { + break; + } + + /* + * If the only work remaining are local ends at the end of the + * block, stop here. Those are implied anyway. + */ + if (next == codeSize + && nextAddrL == Integer.MAX_VALUE + && nextAddrP == Integer.MAX_VALUE) { + break; + } + + if (next == nextAddrP) { + // Combined advance PC + position entry + emitPosition(sortedPositions.get(curPositionIdx++)); + } else { + emitAdvancePc(next - address); + } + } + + emitEndSequence(); + + return output.toByteArray(); + } + + /** + * Emits all local variable activity that occurs at the current + * {@link #address} starting at the given index into {@code + * locals} and including all subsequent activity at the same + * address. + * + * @param curLocalIdx Current index in locals + * @return new value for {@code curLocalIdx} + * @throws IOException + */ + private int emitLocalsAtAddress(int curLocalIdx) + throws IOException { + int sz = locals.size(); + + // TODO: Don't emit ends implied by starts. + + while ((curLocalIdx < sz) + && (locals.get(curLocalIdx).getAddress() == address)) { + LocalList.Entry entry = locals.get(curLocalIdx++); + int reg = entry.getRegister(); + LocalList.Entry prevEntry = lastEntryForReg[reg]; + + if (entry == prevEntry) { + /* + * Here we ignore locals entries for parameters, + * which have already been represented and placed in the + * lastEntryForReg array. + */ + continue; + } + + // At this point we have a new entry one way or another. + lastEntryForReg[reg] = entry; + + if (entry.isStart()) { + if ((prevEntry != null) && entry.matches(prevEntry)) { + /* + * The previous local in this register has the same + * name and type as the one being introduced now, so + * use the more efficient "restart" form. + */ + if (prevEntry.isStart()) { + /* + * We should never be handed a start when a + * a matching local is already active. + */ + throw new RuntimeException("shouldn't happen"); + } + emitLocalRestart(entry); + } else { + emitLocalStart(entry); + } + } else { + /* + * Only emit a local end if it is *not* due to a direct + * replacement. Direct replacements imply an end of the + * previous local in the same register. + * + * TODO: Make sure the runtime can deal with implied + * local ends from category-2 interactions, and when so, + * also stop emitting local ends for those cases. + */ + if (entry.getDisposition() + != LocalList.Disposition.END_REPLACED) { + emitLocalEnd(entry); + } + } + } + + return curLocalIdx; + } + + /** + * Emits all positions that occur at the current {@code address} + * + * @param curPositionIdx Current index in sortedPositions + * @param sortedPositions positions, sorted by ascending address + * @return new value for {@code curPositionIdx} + * @throws IOException + */ + private int emitPositionsAtAddress(int curPositionIdx, + ArrayList sortedPositions) + throws IOException { + int positionsSz = sortedPositions.size(); + while ((curPositionIdx < positionsSz) + && (sortedPositions.get(curPositionIdx).getAddress() + == address)) { + emitPosition(sortedPositions.get(curPositionIdx++)); + } + return curPositionIdx; + } + + /** + * Emits the header sequence, which consists of LEB128-encoded initial + * line number and string indicies for names of all non-"this" arguments. + * + * @param sortedPositions positions, sorted by ascending address + * @param methodArgs local list entries for method argumens arguments, + * in left-to-right order omitting "this" + * @throws IOException + */ + private void emitHeader(ArrayList sortedPositions, + ArrayList methodArgs) throws IOException { + boolean annotate = (annotateTo != null) || (debugPrint != null); + int mark = output.getCursor(); + + // Start by initializing the line number register. + if (sortedPositions.size() > 0) { + PositionList.Entry entry = sortedPositions.get(0); + line = entry.getPosition().getLine(); + } + output.writeUleb128(line); + + if (annotate) { + annotate(output.getCursor() - mark, "line_start: " + line); + } + + int curParam = getParamBase(); + // paramTypes will not include 'this' + StdTypeList paramTypes = desc.getParameterTypes(); + int szParamTypes = paramTypes.size(); + + /* + * Initialize lastEntryForReg to have an initial + * entry for the 'this' pointer. + */ + if (!isStatic) { + for (LocalList.Entry arg : methodArgs) { + if (curParam == arg.getRegister()) { + lastEntryForReg[curParam] = arg; + break; + } + } + curParam++; + } + + // Write out the number of parameter entries that will follow. + mark = output.getCursor(); + output.writeUleb128(szParamTypes); + + if (annotate) { + annotate(output.getCursor() - mark, + String.format("parameters_size: %04x", szParamTypes)); + } + + /* + * Then emit the string indicies of all the method parameters. + * Note that 'this', if applicable, is excluded. + */ + for (int i = 0; i < szParamTypes; i++) { + Type pt = paramTypes.get(i); + LocalList.Entry found = null; + + mark = output.getCursor(); + + for (LocalList.Entry arg : methodArgs) { + if (curParam == arg.getRegister()) { + found = arg; + + if (arg.getSignature() != null) { + /* + * Parameters with signatures will be re-emitted + * in complete as LOCAL_START_EXTENDED's below. + */ + emitStringIndex(null); + } else { + emitStringIndex(arg.getName()); + } + lastEntryForReg[curParam] = arg; + + break; + } + } + + if (found == null) { + /* + * Emit a null symbol for "unnamed." This is common + * for, e.g., synthesized methods and inner-class + * this$0 arguments. + */ + emitStringIndex(null); + } + + if (annotate) { + String parameterName + = (found == null || found.getSignature() != null) + ? "" : found.getName().toHuman(); + annotate(output.getCursor() - mark, + "parameter " + parameterName + " " + + RegisterSpec.PREFIX + curParam); + } + + curParam += pt.getCategory(); + } + + /* + * If anything emitted above has a type signature, emit it again as + * a LOCAL_RESTART_EXTENDED + */ + + for (LocalList.Entry arg : lastEntryForReg) { + if (arg == null) { + continue; + } + + CstString signature = arg.getSignature(); + + if (signature != null) { + emitLocalStartExtended(arg); + } + } + } + + /** + * Builds a list of position entries, sorted by ascending address. + * + * @return A sorted positions list + */ + private ArrayList buildSortedPositions() { + int sz = (positions == null) ? 0 : positions.size(); + ArrayList result = new ArrayList(sz); + + for (int i = 0; i < sz; i++) { + result.add(positions.get(i)); + } + + // Sort ascending by address. + Collections.sort (result, new Comparator() { + public int compare (PositionList.Entry a, PositionList.Entry b) { + return a.getAddress() - b.getAddress(); + } + + public boolean equals (Object obj) { + return obj == this; + } + }); + return result; + } + + /** + * Gets the register that begins the method's parameter range (including + * the 'this' parameter for non-static methods). The range continues until + * {@code regSize} + * + * @return register as noted above + */ + private int getParamBase() { + return regSize + - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1); + } + + /** + * Extracts method arguments from a locals list. These will be collected + * from the input list and sorted by ascending register in the + * returned list. + * + * @return list of non-{@code this} method argument locals, + * sorted by ascending register + */ + private ArrayList extractMethodArguments() { + ArrayList result + = new ArrayList(desc.getParameterTypes().size()); + int argBase = getParamBase(); + BitSet seen = new BitSet(regSize - argBase); + int sz = locals.size(); + + for (int i = 0; i < sz; i++) { + LocalList.Entry e = locals.get(i); + int reg = e.getRegister(); + + if (reg < argBase) { + continue; + } + + // only the lowest-start-address entry is included. + if (seen.get(reg - argBase)) { + continue; + } + + seen.set(reg - argBase); + result.add(e); + } + + // Sort by ascending register. + Collections.sort(result, new Comparator() { + public int compare(LocalList.Entry a, LocalList.Entry b) { + return a.getRegister() - b.getRegister(); + } + + public boolean equals(Object obj) { + return obj == this; + } + }); + + return result; + } + + /** + * Returns a string representation of this LocalList entry that is + * appropriate for emitting as an annotation. + * + * @param e {@code non-null;} entry + * @return {@code non-null;} annotation string + */ + private String entryAnnotationString(LocalList.Entry e) { + StringBuilder sb = new StringBuilder(); + + sb.append(RegisterSpec.PREFIX); + sb.append(e.getRegister()); + sb.append(' '); + + CstString name = e.getName(); + if (name == null) { + sb.append("null"); + } else { + sb.append(name.toHuman()); + } + sb.append(' '); + + CstType type = e.getType(); + if (type == null) { + sb.append("null"); + } else { + sb.append(type.toHuman()); + } + + CstString signature = e.getSignature(); + + if (signature != null) { + sb.append(' '); + sb.append(signature.toHuman()); + } + + return sb.toString(); + } + + /** + * Emits a {@link DebugInfoConstants#DBG_RESTART_LOCAL DBG_RESTART_LOCAL} + * sequence. + * + * @param entry entry associated with this restart + * @throws IOException + */ + private void emitLocalRestart(LocalList.Entry entry) + throws IOException { + + int mark = output.getCursor(); + + output.writeByte(DBG_RESTART_LOCAL); + emitUnsignedLeb128(entry.getRegister()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: +local restart %s", + address, entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local restart"); + } + } + + /** + * Emits a string index as an unsigned LEB128. The actual value written + * is shifted by 1, so that the '0' value is reserved for "null". The + * null symbol is used in some cases by the parameter name list + * at the beginning of the sequence. + * + * @param string {@code null-ok;} string to emit + * @throws IOException + */ + private void emitStringIndex(CstString string) throws IOException { + if ((string == null) || (file == null)) { + output.writeUleb128(0); + } else { + output.writeUleb128( + 1 + file.getStringIds().indexOf(string)); + } + + if (DEBUG) { + System.err.printf("Emit string %s\n", + string == null ? "" : string.toQuoted()); + } + } + + /** + * Emits a type index as an unsigned LEB128. The actual value written + * is shifted by 1, so that the '0' value is reserved for "null". + * + * @param type {@code null-ok;} type to emit + * @throws IOException + */ + private void emitTypeIndex(CstType type) throws IOException { + if ((type == null) || (file == null)) { + output.writeUleb128(0); + } else { + output.writeUleb128( + 1 + file.getTypeIds().indexOf(type)); + } + + if (DEBUG) { + System.err.printf("Emit type %s\n", + type == null ? "" : type.toHuman()); + } + } + + /** + * Emits a {@link DebugInfoConstants#DBG_START_LOCAL DBG_START_LOCAL} or + * {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED + * DBG_START_LOCAL_EXTENDED} sequence. + * + * @param entry entry to emit + * @throws IOException + */ + private void emitLocalStart(LocalList.Entry entry) + throws IOException { + + if (entry.getSignature() != null) { + emitLocalStartExtended(entry); + return; + } + + int mark = output.getCursor(); + + output.writeByte(DBG_START_LOCAL); + + emitUnsignedLeb128(entry.getRegister()); + emitStringIndex(entry.getName()); + emitTypeIndex(entry.getType()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: +local %s", address, + entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local start"); + } + } + + /** + * Emits a {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED + * DBG_START_LOCAL_EXTENDED} sequence. + * + * @param entry entry to emit + * @throws IOException + */ + private void emitLocalStartExtended(LocalList.Entry entry) + throws IOException { + + int mark = output.getCursor(); + + output.writeByte(DBG_START_LOCAL_EXTENDED); + + emitUnsignedLeb128(entry.getRegister()); + emitStringIndex(entry.getName()); + emitTypeIndex(entry.getType()); + emitStringIndex(entry.getSignature()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: +localx %s", address, + entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local start"); + } + } + + /** + * Emits a {@link DebugInfoConstants#DBG_END_LOCAL DBG_END_LOCAL} sequence. + * + * @param entry {@code entry non-null;} entry associated with end. + * @throws IOException + */ + private void emitLocalEnd(LocalList.Entry entry) + throws IOException { + + int mark = output.getCursor(); + + output.writeByte(DBG_END_LOCAL); + output.writeUleb128(entry.getRegister()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: -local %s", address, + entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local end"); + } + } + + /** + * Emits the necessary byte sequences to emit the given position table + * entry. This will typically be a single special opcode, although + * it may also require DBG_ADVANCE_PC or DBG_ADVANCE_LINE. + * + * @param entry position entry to emit. + * @throws IOException + */ + private void emitPosition(PositionList.Entry entry) + throws IOException { + + SourcePosition pos = entry.getPosition(); + int newLine = pos.getLine(); + int newAddress = entry.getAddress(); + + int opcode; + + int deltaLines = newLine - line; + int deltaAddress = newAddress - address; + + if (deltaAddress < 0) { + throw new RuntimeException( + "Position entries must be in ascending address order"); + } + + if ((deltaLines < DBG_LINE_BASE) + || (deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1))) { + emitAdvanceLine(deltaLines); + deltaLines = 0; + } + + opcode = computeOpcode (deltaLines, deltaAddress); + + if ((opcode & ~0xff) > 0) { + emitAdvancePc(deltaAddress); + deltaAddress = 0; + opcode = computeOpcode (deltaLines, deltaAddress); + + if ((opcode & ~0xff) > 0) { + emitAdvanceLine(deltaLines); + deltaLines = 0; + opcode = computeOpcode (deltaLines, deltaAddress); + } + } + + output.writeByte(opcode); + + line += deltaLines; + address += deltaAddress; + + if (annotateTo != null || debugPrint != null) { + annotate(1, + String.format("%04x: line %d", address, line)); + } + } + + /** + * Computes a special opcode that will encode the given position change. + * If the return value is > 0xff, then the request cannot be fulfilled. + * Essentially the same as described in "DWARF Debugging Format Version 3" + * section 6.2.5.1. + * + * @param deltaLines {@code >= DBG_LINE_BASE, <= DBG_LINE_BASE + + * DBG_LINE_RANGE;} the line change to encode + * @param deltaAddress {@code >= 0;} the address change to encode + * @return {@code <= 0xff} if in range, otherwise parameters are out + * of range + */ + private static int computeOpcode(int deltaLines, int deltaAddress) { + if (deltaLines < DBG_LINE_BASE + || deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1)) { + + throw new RuntimeException("Parameter out of range"); + } + + return (deltaLines - DBG_LINE_BASE) + + (DBG_LINE_RANGE * deltaAddress) + DBG_FIRST_SPECIAL; + } + + /** + * Emits an {@link DebugInfoConstants#DBG_ADVANCE_LINE DBG_ADVANCE_LINE} + * sequence. + * + * @param deltaLines amount to change line number register by + * @throws IOException + */ + private void emitAdvanceLine(int deltaLines) throws IOException { + int mark = output.getCursor(); + + output.writeByte(DBG_ADVANCE_LINE); + output.writeSleb128(deltaLines); + line += deltaLines; + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("line = %d", line)); + } + + if (DEBUG) { + System.err.printf("Emitting advance_line for %d\n", deltaLines); + } + } + + /** + * Emits an {@link DebugInfoConstants#DBG_ADVANCE_PC DBG_ADVANCE_PC} + * sequence. + * + * @param deltaAddress {@code >= 0;} amount to change program counter by + * @throws IOException + */ + private void emitAdvancePc(int deltaAddress) throws IOException { + int mark = output.getCursor(); + + output.writeByte(DBG_ADVANCE_PC); + output.writeUleb128(deltaAddress); + address += deltaAddress; + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: advance pc", address)); + } + + if (DEBUG) { + System.err.printf("Emitting advance_pc for %d\n", deltaAddress); + } + } + + /** + * Emits an unsigned LEB128 value. + * + * @param n {@code >= 0;} value to emit. Note that, although this can + * represent integers larger than Integer.MAX_VALUE, we currently don't + * allow that. + * @throws IOException + */ + private void emitUnsignedLeb128(int n) throws IOException { + // We'll never need the top end of the unsigned range anyway. + if (n < 0) { + throw new RuntimeException( + "Signed value where unsigned required: " + n); + } + + output.writeUleb128(n); + } + + /** + * Emits the {@link DebugInfoConstants#DBG_END_SEQUENCE DBG_END_SEQUENCE} + * bytecode. + */ + private void emitEndSequence() { + output.writeByte(DBG_END_SEQUENCE); + + if (annotateTo != null || debugPrint != null) { + annotate(1, "end sequence"); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoItem.java b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoItem.java new file mode 100644 index 000000000..2fd864a33 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/DebugInfoItem.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.dex.code.DalvCode; +import com.android.dx.dex.code.DalvInsnList; +import com.android.dx.dex.code.LocalList; +import com.android.dx.dex.code.PositionList; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.util.AnnotatedOutput; +import java.io.PrintWriter; + +public class DebugInfoItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + private static final boolean ENABLE_ENCODER_SELF_CHECK = false; + + /** {@code non-null;} the code this item represents */ + private final DalvCode code; + + private byte[] encoded; + + private final boolean isStatic; + private final CstMethodRef ref; + + public DebugInfoItem(DalvCode code, boolean isStatic, CstMethodRef ref) { + // We don't know the write size yet. + super (ALIGNMENT, -1); + + if (code == null) { + throw new NullPointerException("code == null"); + } + + this.code = code; + this.isStatic = isStatic; + this.ref = ref; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_DEBUG_INFO_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // No contents to add. + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + try { + encoded = encode(addedTo.getFile(), null, null, null, false); + setWriteSize(encoded.length); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while placing debug info for " + ref.toHuman()); + } + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** + * Writes annotations for the elements of this list, as + * zero-length. This is meant to be used for dumping this instance + * directly after a code dump (with the real local list actually + * existing elsewhere in the output). + * + * @param file {@code non-null;} the file to use for referencing other sections + * @param out {@code non-null;} where to annotate to + * @param prefix {@code null-ok;} prefix to attach to each line of output + */ + public void annotateTo(DexFile file, AnnotatedOutput out, String prefix) { + encode(file, prefix, null, out, false); + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintWriter out, String prefix) { + encode(null, prefix, out, null, false); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + if (out.annotates()) { + /* + * Re-run the encoder to generate the annotations, + * but write the bits from the original encode + */ + + out.annotate(offsetString() + " debug info"); + encode(file, null, null, out, true); + } + + out.write(encoded); + } + + /** + * Performs debug info encoding. + * + * @param file {@code null-ok;} file to refer to during encoding + * @param prefix {@code null-ok;} prefix to attach to each line of output + * @param debugPrint {@code null-ok;} if specified, an alternate output for + * annotations + * @param out {@code null-ok;} if specified, where annotations should go + * @param consume whether to claim to have consumed output for + * {@code out} + * @return {@code non-null;} the encoded array + */ + private byte[] encode(DexFile file, String prefix, PrintWriter debugPrint, + AnnotatedOutput out, boolean consume) { + byte[] result = encode0(file, prefix, debugPrint, out, consume); + + if (ENABLE_ENCODER_SELF_CHECK && (file != null)) { + try { + DebugInfoDecoder.validateEncode(result, file, ref, code, + isStatic); + } catch (RuntimeException ex) { + // Reconvert, annotating to System.err. + encode0(file, "", new PrintWriter(System.err, true), null, + false); + throw ex; + } + } + + return result; + } + + /** + * Helper for {@link #encode} to do most of the work. + * + * @param file {@code null-ok;} file to refer to during encoding + * @param prefix {@code null-ok;} prefix to attach to each line of output + * @param debugPrint {@code null-ok;} if specified, an alternate output for + * annotations + * @param out {@code null-ok;} if specified, where annotations should go + * @param consume whether to claim to have consumed output for + * {@code out} + * @return {@code non-null;} the encoded array + */ + private byte[] encode0(DexFile file, String prefix, PrintWriter debugPrint, + AnnotatedOutput out, boolean consume) { + PositionList positions = code.getPositions(); + LocalList locals = code.getLocals(); + DalvInsnList insns = code.getInsns(); + int codeSize = insns.codeSize(); + int regSize = insns.getRegistersSize(); + + DebugInfoEncoder encoder = + new DebugInfoEncoder(positions, locals, + file, codeSize, regSize, isStatic, ref); + + byte[] result; + + if ((debugPrint == null) && (out == null)) { + result = encoder.convert(); + } else { + result = encoder.convertAndAnnotate(prefix, debugPrint, out, + consume); + } + + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/DexFile.java b/dexlib/src/main/java/com/android/dx/dex/file/DexFile.java new file mode 100644 index 000000000..9b9d70f85 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/DexFile.java @@ -0,0 +1,666 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.dex.DexOptions; +import com.android.dx.dex.file.MixedItemSection.SortType; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstBaseMethodRef; +import com.android.dx.rop.cst.CstEnumRef; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstProtoRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.util.ByteArrayAnnotatedOutput; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.zip.Adler32; + +/** + * Representation of an entire {@code .dex} (Dalvik EXecutable) + * file, which itself consists of a set of Dalvik classes. + */ +public final class DexFile { + /** options controlling the creation of the file */ + private DexOptions dexOptions; + + /** {@code non-null;} word data section */ + private final MixedItemSection wordData; + + /** + * {@code non-null;} type lists section. This is word data, but separating + * it from {@link #wordData} helps break what would otherwise be a + * circular dependency between the that and {@link #protoIds}. + */ + private final MixedItemSection typeLists; + + /** + * {@code non-null;} map section. The map needs to be in a section by itself + * for the self-reference mechanics to work in a reasonably + * straightforward way. See {@link MapItem#addMap} for more detail. + */ + private final MixedItemSection map; + + /** {@code non-null;} string data section */ + private final MixedItemSection stringData; + + /** {@code non-null;} string identifiers section */ + private final StringIdsSection stringIds; + + /** {@code non-null;} type identifiers section */ + private final TypeIdsSection typeIds; + + /** {@code non-null;} prototype identifiers section */ + private final ProtoIdsSection protoIds; + + /** {@code non-null;} field identifiers section */ + private final FieldIdsSection fieldIds; + + /** {@code non-null;} method identifiers section */ + private final MethodIdsSection methodIds; + + /** {@code non-null;} class definitions section */ + private final ClassDefsSection classDefs; + + /** {@code non-null;} class data section */ + private final MixedItemSection classData; + + /** {@code non-null;} byte data section */ + private final MixedItemSection byteData; + + /** {@code non-null;} file header */ + private final HeaderSection header; + + /** + * {@code non-null;} array of sections in the order they will appear in the + * final output file + */ + private final Section[] sections; + + /** {@code >= -1;} total file size or {@code -1} if unknown */ + private int fileSize; + + /** {@code >= 40;} maximum width of the file dump */ + private int dumpWidth; + + /** + * Constructs an instance. It is initially empty. + */ + public DexFile(DexOptions dexOptions) { + this.dexOptions = dexOptions; + + header = new HeaderSection(this); + typeLists = new MixedItemSection(null, this, 4, SortType.NONE); + wordData = new MixedItemSection("word_data", this, 4, SortType.TYPE); + stringData = + new MixedItemSection("string_data", this, 1, SortType.INSTANCE); + classData = new MixedItemSection(null, this, 1, SortType.NONE); + byteData = new MixedItemSection("byte_data", this, 1, SortType.TYPE); + stringIds = new StringIdsSection(this); + typeIds = new TypeIdsSection(this); + protoIds = new ProtoIdsSection(this); + fieldIds = new FieldIdsSection(this); + methodIds = new MethodIdsSection(this); + classDefs = new ClassDefsSection(this); + map = new MixedItemSection("map", this, 4, SortType.NONE); + + /* + * This is the list of sections in the order they appear in + * the final output. + */ + sections = new Section[] { + header, stringIds, typeIds, protoIds, fieldIds, methodIds, + classDefs, wordData, typeLists, stringData, byteData, + classData, map }; + + fileSize = -1; + dumpWidth = 79; + } + + /** + * Returns true if this dex doesn't contain any class defs. + */ + public boolean isEmpty() { + return classDefs.items().isEmpty(); + } + + /** + * Gets the dex-creation options object. + */ + public DexOptions getDexOptions() { + return dexOptions; + } + + /** + * Adds a class to this instance. It is illegal to attempt to add more + * than one class with the same name. + * + * @param clazz {@code non-null;} the class to add + */ + public void add(ClassDefItem clazz) { + classDefs.add(clazz); + } + + /** + * Gets the class definition with the given name, if any. + * + * @param name {@code non-null;} the class name to look for + * @return {@code null-ok;} the class with the given name, or {@code null} + * if there is no such class + */ + public ClassDefItem getClassOrNull(String name) { + try { + Type type = Type.internClassName(name); + return (ClassDefItem) classDefs.get(new CstType(type)); + } catch (IllegalArgumentException ex) { + // Translate exception, per contract. + return null; + } + } + + /** + * Writes the contents of this instance as either a binary or a + * human-readable form, or both. + * + * @param out {@code null-ok;} where to write to + * @param humanOut {@code null-ok;} where to write human-oriented output to + * @param verbose whether to be verbose when writing human-oriented output + */ + public void writeTo(OutputStream out, Writer humanOut, boolean verbose) + throws IOException { + boolean annotate = (humanOut != null); + ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); + + if (out != null) { + out.write(result.getArray()); + } + + if (annotate) { + result.writeAnnotationsTo(humanOut); + } + } + + /** + * Returns the contents of this instance as a {@code .dex} file, + * in {@code byte[]} form. + * + * @param humanOut {@code null-ok;} where to write human-oriented output to + * @param verbose whether to be verbose when writing human-oriented output + * @return {@code non-null;} a {@code .dex} file for this instance + */ + public byte[] toDex(Writer humanOut, boolean verbose) + throws IOException { + boolean annotate = (humanOut != null); + ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); + + if (annotate) { + result.writeAnnotationsTo(humanOut); + } + + return result.getArray(); + } + + /** + * Sets the maximum width of the human-oriented dump of the instance. + * + * @param dumpWidth {@code >= 40;} the width + */ + public void setDumpWidth(int dumpWidth) { + if (dumpWidth < 40) { + throw new IllegalArgumentException("dumpWidth < 40"); + } + + this.dumpWidth = dumpWidth; + } + + /** + * Gets the total file size, if known. + * + *

This is package-scope in order to allow + * the {@link HeaderSection} to set itself up properly.

+ * + * @return {@code >= 0;} the total file size + * @throws RuntimeException thrown if the file size is not yet known + */ + public int getFileSize() { + if (fileSize < 0) { + throw new RuntimeException("file size not yet known"); + } + + return fileSize; + } + + /** + * Gets the string data section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the string data section + */ + /*package*/ MixedItemSection getStringData() { + return stringData; + } + + /** + * Gets the word data section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the word data section + */ + /*package*/ MixedItemSection getWordData() { + return wordData; + } + + /** + * Gets the type lists section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the word data section + */ + /*package*/ MixedItemSection getTypeLists() { + return typeLists; + } + + /** + * Gets the map section. + * + *

This is package-scope in order to allow the header section + * to query it.

+ * + * @return {@code non-null;} the map section + */ + /*package*/ MixedItemSection getMap() { + return map; + } + + /** + * Gets the string identifiers section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the string identifiers section + */ + /*package*/ StringIdsSection getStringIds() { + return stringIds; + } + + /** + * Gets the class definitions section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the class definitions section + */ + public ClassDefsSection getClassDefs() { + return classDefs; + } + + /** + * Gets the class data section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the class data section + */ + /*package*/ MixedItemSection getClassData() { + return classData; + } + + /** + * Gets the type identifiers section. + * + *

This is public in order to allow + * the various {@link Item} instances to add items to the + * instance and help early counting of type ids.

+ * + * @return {@code non-null;} the class identifiers section + */ + public TypeIdsSection getTypeIds() { + return typeIds; + } + + /** + * Gets the prototype identifiers section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the prototype identifiers section + */ + /*package*/ ProtoIdsSection getProtoIds() { + return protoIds; + } + + /** + * Gets the field identifiers section. + * + *

This is public in order to allow + * the various {@link Item} instances to add items to the + * instance and help early counting of field ids.

+ * + * @return {@code non-null;} the field identifiers section + */ + public FieldIdsSection getFieldIds() { + return fieldIds; + } + + /** + * Gets the method identifiers section. + * + *

This is public in order to allow + * the various {@link Item} instances to add items to the + * instance and help early counting of method ids.

+ * + * @return {@code non-null;} the method identifiers section + */ + public MethodIdsSection getMethodIds() { + return methodIds; + } + + /** + * Gets the byte data section. + * + *

This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.

+ * + * @return {@code non-null;} the byte data section + */ + /*package*/ MixedItemSection getByteData() { + return byteData; + } + + /** + * Gets the first section of the file that is to be considered + * part of the data section. + * + *

This is package-scope in order to allow the header section + * to query it.

+ * + * @return {@code non-null;} the section + */ + /*package*/ Section getFirstDataSection() { + return wordData; + } + + /** + * Gets the last section of the file that is to be considered + * part of the data section. + * + *

This is package-scope in order to allow the header section + * to query it.

+ * + * @return {@code non-null;} the section + */ + /*package*/ Section getLastDataSection() { + return map; + } + + /** + * Interns the given constant in the appropriate section of this + * instance, or do nothing if the given constant isn't the sort + * that should be interned. + * + * @param cst {@code non-null;} constant to possibly intern + */ + /*package*/ void internIfAppropriate(Constant cst) { + if (cst instanceof CstString) { + stringIds.intern((CstString) cst); + } else if (cst instanceof CstType) { + typeIds.intern((CstType) cst); + } else if (cst instanceof CstBaseMethodRef) { + methodIds.intern((CstBaseMethodRef) cst); + } else if (cst instanceof CstFieldRef) { + fieldIds.intern((CstFieldRef) cst); + } else if (cst instanceof CstEnumRef) { + fieldIds.intern(((CstEnumRef) cst).getFieldRef()); + } else if (cst == null) { + throw new NullPointerException("cst == null"); + } + } + + /** + * Gets the {@link IndexedItem} corresponding to the given constant, + * if it is a constant that has such a correspondence, or return + * {@code null} if it isn't such a constant. This will throw + * an exception if the given constant should have been found + * but wasn't. + * + * @param cst {@code non-null;} the constant to look up + * @return {@code null-ok;} its corresponding item, if it has a corresponding + * item, or {@code null} if it's not that sort of constant + */ + /*package*/ IndexedItem findItemOrNull(Constant cst) { + IndexedItem item; + + if (cst instanceof CstString) { + return stringIds.get(cst); + } else if (cst instanceof CstType) { + return typeIds.get(cst); + } else if (cst instanceof CstBaseMethodRef) { + return methodIds.get(cst); + } else if (cst instanceof CstFieldRef) { + return fieldIds.get(cst); + } else if (cst instanceof CstProtoRef) { + return protoIds.get(cst); + } else { + return null; + } + } + + /** + * Returns the contents of this instance as a {@code .dex} file, + * in a {@link ByteArrayAnnotatedOutput} instance. + * + * @param annotate whether or not to keep annotations + * @param verbose if annotating, whether to be verbose + * @return {@code non-null;} a {@code .dex} file for this instance + */ + private ByteArrayAnnotatedOutput toDex0(boolean annotate, + boolean verbose) { + /* + * The following is ordered so that the prepare() calls which + * add items happen before the calls to the sections that get + * added to. + */ + + classDefs.prepare(); + classData.prepare(); + wordData.prepare(); + byteData.prepare(); + methodIds.prepare(); + fieldIds.prepare(); + protoIds.prepare(); + typeLists.prepare(); + typeIds.prepare(); + stringIds.prepare(); + stringData.prepare(); + header.prepare(); + + // Place the sections within the file. + + int count = sections.length; + int offset = 0; + + for (int i = 0; i < count; i++) { + Section one = sections[i]; + int placedAt = one.setFileOffset(offset); + if (placedAt < offset) { + throw new RuntimeException("bogus placement for section " + i); + } + + try { + if (one == map) { + /* + * Inform the map of all the sections, and add it + * to the file. This can only be done after all + * the other items have been sorted and placed. + */ + MapItem.addMap(sections, map); + map.prepare(); + } + + if (one instanceof MixedItemSection) { + /* + * Place the items of a MixedItemSection that just + * got placed. + */ + ((MixedItemSection) one).placeItems(); + } + + offset = placedAt + one.writeSize(); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing section " + i); + } + } + + // Write out all the sections. + + fileSize = offset; + byte[] barr = new byte[fileSize]; + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(barr); + + if (annotate) { + out.enableAnnotations(dumpWidth, verbose); + } + + for (int i = 0; i < count; i++) { + try { + Section one = sections[i]; + int zeroCount = one.getFileOffset() - out.getCursor(); + if (zeroCount < 0) { + throw new ExceptionWithContext("excess write of " + + (-zeroCount)); + } + out.writeZeroes(one.getFileOffset() - out.getCursor()); + one.writeTo(out); + } catch (RuntimeException ex) { + ExceptionWithContext ec; + if (ex instanceof ExceptionWithContext) { + ec = (ExceptionWithContext) ex; + } else { + ec = new ExceptionWithContext(ex); + } + ec.addContext("...while writing section " + i); + throw ec; + } + } + + if (out.getCursor() != fileSize) { + throw new RuntimeException("foreshortened write"); + } + + // Perform final bookkeeping. + + calcSignature(barr); + calcChecksum(barr); + + if (annotate) { + wordData.writeIndexAnnotation(out, ItemType.TYPE_CODE_ITEM, + "\nmethod code index:\n\n"); + getStatistics().writeAnnotation(out); + out.finishAnnotating(); + } + + return out; + } + + /** + * Generates and returns statistics for all the items in the file. + * + * @return {@code non-null;} the statistics + */ + public Statistics getStatistics() { + Statistics stats = new Statistics(); + + for (Section s : sections) { + stats.addAll(s); + } + + return stats; + } + + /** + * Calculates the signature for the {@code .dex} file in the + * given array, and modify the array to contain it. + * + * @param bytes {@code non-null;} the bytes of the file + */ + private static void calcSignature(byte[] bytes) { + MessageDigest md; + + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + + md.update(bytes, 32, bytes.length - 32); + + try { + int amt = md.digest(bytes, 12, 20); + if (amt != 20) { + throw new RuntimeException("unexpected digest write: " + amt + + " bytes"); + } + } catch (DigestException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Calculates the checksum for the {@code .dex} file in the + * given array, and modify the array to contain it. + * + * @param bytes {@code non-null;} the bytes of the file + */ + private static void calcChecksum(byte[] bytes) { + Adler32 a32 = new Adler32(); + + a32.update(bytes, 12, bytes.length - 12); + + int sum = (int) a32.getValue(); + + bytes[8] = (byte) sum; + bytes[9] = (byte) (sum >> 8); + bytes[10] = (byte) (sum >> 16); + bytes[11] = (byte) (sum >> 24); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/EncodedArrayItem.java b/dexlib/src/main/java/com/android/dx/dex/file/EncodedArrayItem.java new file mode 100644 index 000000000..12d4b1736 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/EncodedArrayItem.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.CstArray; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.ByteArrayAnnotatedOutput; + +/** + * Encoded array of constant values. + */ +public final class EncodedArrayItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + /** {@code non-null;} the array to represent */ + private final CstArray array; + + /** + * {@code null-ok;} encoded form, ready for writing to a file; set during + * {@link #place0} + */ + private byte[] encodedForm; + + /** + * Constructs an instance. + * + * @param array {@code non-null;} array to represent + */ + public EncodedArrayItem(CstArray array) { + /* + * The write size isn't known up-front because (the variable-lengthed) + * leb128 type is used to represent some things. + */ + super(ALIGNMENT, -1); + + if (array == null) { + throw new NullPointerException("array == null"); + } + + this.array = array; + this.encodedForm = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ENCODED_ARRAY_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return array.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + EncodedArrayItem otherArray = (EncodedArrayItem) other; + + return array.compareTo(otherArray.array); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return array.toHuman(); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + ValueEncoder.addContents(file, array); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + ValueEncoder encoder = new ValueEncoder(addedTo.getFile(), out); + + encoder.writeArray(array, false); + encodedForm = out.toByteArray(); + setWriteSize(encodedForm.length); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + + if (annotates) { + out.annotate(0, offsetString() + " encoded array"); + + /* + * The output is to be annotated, so redo the work previously + * done by place0(), except this time annotations will actually + * get emitted. + */ + ValueEncoder encoder = new ValueEncoder(file, out); + encoder.writeArray(array, true); + } else { + out.write(encodedForm); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/EncodedField.java b/dexlib/src/main/java/com/android/dx/dex/file/EncodedField.java new file mode 100644 index 000000000..317c97961 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/EncodedField.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.Leb128; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.io.PrintWriter; + +/** + * Representation of a field of a class, of any sort. + */ +public final class EncodedField extends EncodedMember + implements Comparable { + /** {@code non-null;} constant for the field */ + private final CstFieldRef field; + + /** + * Constructs an instance. + * + * @param field {@code non-null;} constant for the field + * @param accessFlags access flags + */ + public EncodedField(CstFieldRef field, int accessFlags) { + super(accessFlags); + + if (field == null) { + throw new NullPointerException("field == null"); + } + + /* + * TODO: Maybe check accessFlags, at least for + * easily-checked stuff? + */ + + this.field = field; + } + + /** {@inheritDoc} */ + public int hashCode() { + return field.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof EncodedField)) { + return false; + } + + return compareTo((EncodedField) other) == 0; + } + + /** + * {@inheritDoc} + * + *

Note: This compares the method constants only, + * ignoring any associated code, because it should never be the + * case that two different items with the same method constant + * ever appear in the same list (or same file, even).

+ */ + public int compareTo(EncodedField other) { + return field.compareTo(other.field); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(Hex.u2(getAccessFlags())); + sb.append(' '); + sb.append(field); + sb.append('}'); + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + FieldIdsSection fieldIds = file.getFieldIds(); + fieldIds.intern(field); + } + + /** {@inheritDoc} */ + @Override + public CstString getName() { + return field.getNat().getName(); + } + + /** {@inheritDoc} */ + public String toHuman() { + return field.toHuman(); + } + + /** {@inheritDoc} */ + @Override + public void debugPrint(PrintWriter out, boolean verbose) { + // TODO: Maybe put something better here? + out.println(toString()); + } + + /** + * Gets the constant for the field. + * + * @return {@code non-null;} the constant + */ + public CstFieldRef getRef() { + return field; + } + + /** {@inheritDoc} */ + @Override + public int encode(DexFile file, AnnotatedOutput out, + int lastIndex, int dumpSeq) { + int fieldIdx = file.getFieldIds().indexOf(field); + int diff = fieldIdx - lastIndex; + int accessFlags = getAccessFlags(); + + if (out.annotates()) { + out.annotate(0, String.format(" [%x] %s", dumpSeq, + field.toHuman())); + out.annotate(Leb128.unsignedLeb128Size(diff), + " field_idx: " + Hex.u4(fieldIdx)); + out.annotate(Leb128.unsignedLeb128Size(accessFlags), + " access_flags: " + + AccessFlags.fieldString(accessFlags)); + } + + out.writeUleb128(diff); + out.writeUleb128(accessFlags); + + return fieldIdx; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/EncodedMember.java b/dexlib/src/main/java/com/android/dx/dex/file/EncodedMember.java new file mode 100644 index 000000000..a681d822c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/EncodedMember.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.ToHuman; +import java.io.PrintWriter; + +/** + * Representation of a member (field or method) of a class, for the + * purposes of encoding it inside a {@link ClassDataItem}. + */ +public abstract class EncodedMember implements ToHuman { + /** access flags */ + private final int accessFlags; + + /** + * Constructs an instance. + * + * @param accessFlags access flags for the member + */ + public EncodedMember(int accessFlags) { + this.accessFlags = accessFlags; + } + + /** + * Gets the access flags. + * + * @return the access flags + */ + public final int getAccessFlags() { + return accessFlags; + } + + /** + * Gets the name. + * + * @return {@code non-null;} the name + */ + public abstract CstString getName(); + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param verbose whether to be verbose with the output + */ + public abstract void debugPrint(PrintWriter out, boolean verbose); + + /** + * Populates a {@link DexFile} with items from within this instance. + * + * @param file {@code non-null;} the file to populate + */ + public abstract void addContents(DexFile file); + + /** + * Encodes this instance to the given output. + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + * @param lastIndex {@code >= 0;} the previous member index value encoded, or + * {@code 0} if this is the first element to encode + * @param dumpSeq {@code >= 0;} sequence number of this instance for + * annotation purposes + * @return {@code >= 0;} the member index value that was encoded + */ + public abstract int encode(DexFile file, AnnotatedOutput out, + int lastIndex, int dumpSeq); +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/EncodedMethod.java b/dexlib/src/main/java/com/android/dx/dex/file/EncodedMethod.java new file mode 100644 index 000000000..0a177e650 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/EncodedMethod.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.Leb128; +import com.android.dx.dex.code.DalvCode; +import com.android.dx.rop.code.AccessFlags; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.io.PrintWriter; + +/** + * Class that representats a method of a class. + */ +public final class EncodedMethod extends EncodedMember + implements Comparable { + /** {@code non-null;} constant for the method */ + private final CstMethodRef method; + + /** + * {@code null-ok;} code for the method, if the method is neither + * {@code abstract} nor {@code native} + */ + private final CodeItem code; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} constant for the method + * @param accessFlags access flags + * @param code {@code null-ok;} code for the method, if it is neither + * {@code abstract} nor {@code native} + * @param throwsList {@code non-null;} list of possibly-thrown exceptions, + * just used in generating debugging output (listings) + */ + public EncodedMethod(CstMethodRef method, int accessFlags, + DalvCode code, TypeList throwsList) { + super(accessFlags); + + if (method == null) { + throw new NullPointerException("method == null"); + } + + this.method = method; + + if (code == null) { + this.code = null; + } else { + boolean isStatic = (accessFlags & AccessFlags.ACC_STATIC) != 0; + this.code = new CodeItem(method, code, isStatic, throwsList); + } + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof EncodedMethod)) { + return false; + } + + return compareTo((EncodedMethod) other) == 0; + } + + /** + * {@inheritDoc} + * + *

Note: This compares the method constants only, + * ignoring any associated code, because it should never be the + * case that two different items with the same method constant + * ever appear in the same list (or same file, even).

+ */ + public int compareTo(EncodedMethod other) { + return method.compareTo(other.method); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(Hex.u2(getAccessFlags())); + sb.append(' '); + sb.append(method); + + if (code != null) { + sb.append(' '); + sb.append(code); + } + + sb.append('}'); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + MethodIdsSection methodIds = file.getMethodIds(); + MixedItemSection wordData = file.getWordData(); + + methodIds.intern(method); + + if (code != null) { + wordData.add(code); + } + } + + /** {@inheritDoc} */ + public final String toHuman() { + return method.toHuman(); + } + + /** {@inheritDoc} */ + @Override + public final CstString getName() { + return method.getNat().getName(); + } + + /** {@inheritDoc} */ + @Override + public void debugPrint(PrintWriter out, boolean verbose) { + if (code == null) { + out.println(getRef().toHuman() + ": abstract or native"); + } else { + code.debugPrint(out, " ", verbose); + } + } + + /** + * Gets the constant for the method. + * + * @return {@code non-null;} the constant + */ + public final CstMethodRef getRef() { + return method; + } + + /** {@inheritDoc} */ + @Override + public int encode(DexFile file, AnnotatedOutput out, + int lastIndex, int dumpSeq) { + int methodIdx = file.getMethodIds().indexOf(method); + int diff = methodIdx - lastIndex; + int accessFlags = getAccessFlags(); + int codeOff = OffsettedItem.getAbsoluteOffsetOr0(code); + boolean hasCode = (codeOff != 0); + boolean shouldHaveCode = (accessFlags & + (AccessFlags.ACC_ABSTRACT | AccessFlags.ACC_NATIVE)) == 0; + + /* + * Verify that code appears if and only if a method is + * declared to have it. + */ + if (hasCode != shouldHaveCode) { + throw new UnsupportedOperationException( + "code vs. access_flags mismatch"); + } + + if (out.annotates()) { + out.annotate(0, String.format(" [%x] %s", dumpSeq, + method.toHuman())); + out.annotate(Leb128.unsignedLeb128Size(diff), + " method_idx: " + Hex.u4(methodIdx)); + out.annotate(Leb128.unsignedLeb128Size(accessFlags), + " access_flags: " + + AccessFlags.methodString(accessFlags)); + out.annotate(Leb128.unsignedLeb128Size(codeOff), + " code_off: " + Hex.u4(codeOff)); + } + + out.writeUleb128(diff); + out.writeUleb128(accessFlags); + out.writeUleb128(codeOff); + + return methodIdx; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/FieldAnnotationStruct.java b/dexlib/src/main/java/com/android/dx/dex/file/FieldAnnotationStruct.java new file mode 100644 index 000000000..f363d413c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/FieldAnnotationStruct.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import com.android.dx.util.ToHuman; + +/** + * Association of a field and its annotations. + */ +public final class FieldAnnotationStruct + implements ToHuman, Comparable { + /** {@code non-null;} the field in question */ + private final CstFieldRef field; + + /** {@code non-null;} the associated annotations */ + private AnnotationSetItem annotations; + + /** + * Constructs an instance. + * + * @param field {@code non-null;} the field in question + * @param annotations {@code non-null;} the associated annotations + */ + public FieldAnnotationStruct(CstFieldRef field, + AnnotationSetItem annotations) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + this.field = field; + this.annotations = annotations; + } + + /** {@inheritDoc} */ + public int hashCode() { + return field.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof FieldAnnotationStruct)) { + return false; + } + + return field.equals(((FieldAnnotationStruct) other).field); + } + + /** {@inheritDoc} */ + public int compareTo(FieldAnnotationStruct other) { + return field.compareTo(other.field); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + FieldIdsSection fieldIds = file.getFieldIds(); + MixedItemSection wordData = file.getWordData(); + + fieldIds.intern(field); + annotations = wordData.intern(annotations); + } + + /** {@inheritDoc} */ + public void writeTo(DexFile file, AnnotatedOutput out) { + int fieldIdx = file.getFieldIds().indexOf(field); + int annotationsOff = annotations.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, " " + field.toHuman()); + out.annotate(4, " field_idx: " + Hex.u4(fieldIdx)); + out.annotate(4, " annotations_off: " + + Hex.u4(annotationsOff)); + } + + out.writeInt(fieldIdx); + out.writeInt(annotationsOff); + } + + /** {@inheritDoc} */ + public String toHuman() { + return field.toHuman() + ": " + annotations; + } + + /** + * Gets the field this item is for. + * + * @return {@code non-null;} the field + */ + public CstFieldRef getField() { + return field; + } + + /** + * Gets the associated annotations. + * + * @return {@code non-null;} the annotations + */ + public Annotations getAnnotations() { + return annotations.getAnnotations(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/FieldIdItem.java b/dexlib/src/main/java/com/android/dx/dex/file/FieldIdItem.java new file mode 100644 index 000000000..ecb1d3df8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/FieldIdItem.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.CstFieldRef; + +/** + * Representation of a field reference inside a Dalvik file. + */ +public final class FieldIdItem extends MemberIdItem { + /** + * Constructs an instance. + * + * @param field {@code non-null;} the constant for the field + */ + public FieldIdItem(CstFieldRef field) { + super(field); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_FIELD_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + super.addContents(file); + + TypeIdsSection typeIds = file.getTypeIds(); + typeIds.intern(getFieldRef().getType()); + } + + /** + * Gets the field constant. + * + * @return {@code non-null;} the constant + */ + public CstFieldRef getFieldRef() { + return (CstFieldRef) getRef(); + } + + /** {@inheritDoc} */ + @Override + protected int getTypoidIdx(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + return typeIds.indexOf(getFieldRef().getType()); + } + + /** {@inheritDoc} */ + @Override + protected String getTypoidName() { + return "type_idx"; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/FieldIdsSection.java b/dexlib/src/main/java/com/android/dx/dex/file/FieldIdsSection.java new file mode 100644 index 000000000..f422bab04 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/FieldIdsSection.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Field refs list section of a {@code .dex} file. + */ +public final class FieldIdsSection extends MemberIdsSection { + /** + * {@code non-null;} map from field constants to {@link + * FieldIdItem} instances + */ + private final TreeMap fieldIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public FieldIdsSection(DexFile file) { + super("field_ids", file); + + fieldIds = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return fieldIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + IndexedItem result = fieldIds.get((CstFieldRef) cst); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = fieldIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "field_ids_size: " + Hex.u4(sz)); + out.annotate(4, "field_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param field {@code non-null;} the reference to intern + * @return {@code non-null;} the interned reference + */ + public synchronized FieldIdItem intern(CstFieldRef field) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + throwIfPrepared(); + + FieldIdItem result = fieldIds.get(field); + + if (result == null) { + result = new FieldIdItem(field); + fieldIds.put(field, result); + } + + return result; + } + + /** + * Gets the index of the given reference, which must have been added + * to this instance. + * + * @param ref {@code non-null;} the reference to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(CstFieldRef ref) { + if (ref == null) { + throw new NullPointerException("ref == null"); + } + + throwIfNotPrepared(); + + FieldIdItem item = fieldIds.get(ref); + + if (item == null) { + throw new IllegalArgumentException("not found"); + } + + return item.getIndex(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/HeaderItem.java b/dexlib/src/main/java/com/android/dx/dex/file/HeaderItem.java new file mode 100644 index 000000000..f404c3e8a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/HeaderItem.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.DexFormat; +import com.android.dex.SizeOf; +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +/** + * File header section of a {@code .dex} file. + */ +public final class HeaderItem extends IndexedItem { + /** + * Constructs an instance. + */ + public HeaderItem() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_HEADER_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.HEADER_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // Nothing to do here. + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + int mapOff = file.getMap().getFileOffset(); + Section firstDataSection = file.getFirstDataSection(); + Section lastDataSection = file.getLastDataSection(); + int dataOff = firstDataSection.getFileOffset(); + int dataSize = lastDataSection.getFileOffset() + + lastDataSection.writeSize() - dataOff; + + String magic = file.getDexOptions().getMagic(); + + if (out.annotates()) { + out.annotate(8, "magic: " + new CstString(magic).toQuoted()); + out.annotate(4, "checksum"); + out.annotate(20, "signature"); + out.annotate(4, "file_size: " + + Hex.u4(file.getFileSize())); + out.annotate(4, "header_size: " + Hex.u4(SizeOf.HEADER_ITEM)); + out.annotate(4, "endian_tag: " + Hex.u4(DexFormat.ENDIAN_TAG)); + out.annotate(4, "link_size: 0"); + out.annotate(4, "link_off: 0"); + out.annotate(4, "map_off: " + Hex.u4(mapOff)); + } + + // Write the magic number. + for (int i = 0; i < 8; i++) { + out.writeByte(magic.charAt(i)); + } + + // Leave space for the checksum and signature. + out.writeZeroes(24); + + out.writeInt(file.getFileSize()); + out.writeInt(SizeOf.HEADER_ITEM); + out.writeInt(DexFormat.ENDIAN_TAG); + + /* + * Write zeroes for the link size and data, as the output + * isn't a staticly linked file. + */ + out.writeZeroes(8); + + out.writeInt(mapOff); + + // Write out each section's respective header part. + file.getStringIds().writeHeaderPart(out); + file.getTypeIds().writeHeaderPart(out); + file.getProtoIds().writeHeaderPart(out); + file.getFieldIds().writeHeaderPart(out); + file.getMethodIds().writeHeaderPart(out); + file.getClassDefs().writeHeaderPart(out); + + if (out.annotates()) { + out.annotate(4, "data_size: " + Hex.u4(dataSize)); + out.annotate(4, "data_off: " + Hex.u4(dataOff)); + } + + out.writeInt(dataSize); + out.writeInt(dataOff); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/HeaderSection.java b/dexlib/src/main/java/com/android/dx/dex/file/HeaderSection.java new file mode 100644 index 000000000..e3de356b6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/HeaderSection.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.Constant; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * File header section of a {@code .dex} file. + */ +public final class HeaderSection extends UniformItemSection { + /** {@code non-null;} the list of the one item in the section */ + private final List list; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public HeaderSection(DexFile file) { + super(null, file, 4); + + HeaderItem item = new HeaderItem(); + item.setIndex(0); + + this.list = Collections.singletonList(item); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + return null; + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return list; + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + // Nothing to do here. + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/IdItem.java b/dexlib/src/main/java/com/android/dx/dex/file/IdItem.java new file mode 100644 index 000000000..1bd2b5faa --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/IdItem.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.CstType; + +/** + * Representation of a reference to an item inside a Dalvik file. + */ +public abstract class IdItem extends IndexedItem { + /** + * {@code non-null;} the type constant for the defining class of + * the reference + */ + private final CstType type; + + /** + * Constructs an instance. + * + * @param type {@code non-null;} the type constant for the defining + * class of the reference + */ + public IdItem(CstType type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + this.type = type; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + typeIds.intern(type); + } + + /** + * Gets the type constant for the defining class of the + * reference. + * + * @return {@code non-null;} the type constant + */ + public final CstType getDefiningClass() { + return type; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/IndexedItem.java b/dexlib/src/main/java/com/android/dx/dex/file/IndexedItem.java new file mode 100644 index 000000000..9ba478340 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/IndexedItem.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +/** + * An item in a Dalvik file which is referenced by index. + */ +public abstract class IndexedItem extends Item { + /** {@code >= -1;} assigned index of the item, or {@code -1} if not + * yet assigned */ + private int index; + + /** + * Constructs an instance. The index is initially unassigned. + */ + public IndexedItem() { + index = -1; + } + + /** + * Gets whether or not this instance has been assigned an index. + * + * @return {@code true} iff this instance has been assigned an index + */ + public final boolean hasIndex() { + return (index >= 0); + } + + /** + * Gets the item index. + * + * @return {@code >= 0;} the index + * @throws RuntimeException thrown if the item index is not yet assigned + */ + public final int getIndex() { + if (index < 0) { + throw new RuntimeException("index not yet set"); + } + + return index; + } + + /** + * Sets the item index. This method may only ever be called once + * per instance, and this will throw a {@code RuntimeException} if + * called a second (or subsequent) time. + * + * @param index {@code >= 0;} the item index + */ + public final void setIndex(int index) { + if (this.index != -1) { + throw new RuntimeException("index already set"); + } + + this.index = index; + } + + /** + * Gets the index of this item as a string, suitable for including in + * annotations. + * + * @return {@code non-null;} the index string + */ + public final String indexString() { + return '[' + Integer.toHexString(index) + ']'; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/Item.java b/dexlib/src/main/java/com/android/dx/dex/file/Item.java new file mode 100644 index 000000000..cf2b380c3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/Item.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.util.AnnotatedOutput; + +/** + * Base class for any structurally-significant and (potentially) + * repeated piece of a Dalvik file. + */ +public abstract class Item { + /** + * Constructs an instance. + */ + public Item() { + // This space intentionally left blank. + } + + /** + * Returns the item type for this instance. + * + * @return {@code non-null;} the item type + */ + public abstract ItemType itemType(); + + /** + * Returns the human name for the particular type of item this + * instance is. + * + * @return {@code non-null;} the name + */ + public final String typeName() { + return itemType().toHuman(); + } + + /** + * Gets the size of this instance when written, in bytes. + * + * @return {@code >= 0;} the write size + */ + public abstract int writeSize(); + + /** + * Populates a {@link DexFile} with items from within this instance. + * This will not add an item to the file for this instance itself + * (which should have been done by whatever refers to this instance). + * + *

Note: Subclasses must override this to do something + * appropriate.

+ * + * @param file {@code non-null;} the file to populate + */ + public abstract void addContents(DexFile file); + + /** + * Writes the representation of this instance to the given data section, + * using the given {@link DexFile} to look things up as needed. + * If this instance keeps track of its offset, then this method will + * note the written offset and will also throw an exception if this + * instance has already been written. + * + * @param file {@code non-null;} the file to use for reference + * @param out {@code non-null;} where to write to + */ + public abstract void writeTo(DexFile file, AnnotatedOutput out); +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/ItemType.java b/dexlib/src/main/java/com/android/dx/dex/file/ItemType.java new file mode 100644 index 000000000..2fe97ab25 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/ItemType.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.util.ToHuman; + +/** + * Enumeration of all the top-level item types. + */ +public enum ItemType implements ToHuman { + TYPE_HEADER_ITEM( 0x0000, "header_item"), + TYPE_STRING_ID_ITEM( 0x0001, "string_id_item"), + TYPE_TYPE_ID_ITEM( 0x0002, "type_id_item"), + TYPE_PROTO_ID_ITEM( 0x0003, "proto_id_item"), + TYPE_FIELD_ID_ITEM( 0x0004, "field_id_item"), + TYPE_METHOD_ID_ITEM( 0x0005, "method_id_item"), + TYPE_CLASS_DEF_ITEM( 0x0006, "class_def_item"), + TYPE_MAP_LIST( 0x1000, "map_list"), + TYPE_TYPE_LIST( 0x1001, "type_list"), + TYPE_ANNOTATION_SET_REF_LIST( 0x1002, "annotation_set_ref_list"), + TYPE_ANNOTATION_SET_ITEM( 0x1003, "annotation_set_item"), + TYPE_CLASS_DATA_ITEM( 0x2000, "class_data_item"), + TYPE_CODE_ITEM( 0x2001, "code_item"), + TYPE_STRING_DATA_ITEM( 0x2002, "string_data_item"), + TYPE_DEBUG_INFO_ITEM( 0x2003, "debug_info_item"), + TYPE_ANNOTATION_ITEM( 0x2004, "annotation_item"), + TYPE_ENCODED_ARRAY_ITEM( 0x2005, "encoded_array_item"), + TYPE_ANNOTATIONS_DIRECTORY_ITEM(0x2006, "annotations_directory_item"), + TYPE_MAP_ITEM( -1, "map_item"), + TYPE_TYPE_ITEM( -1, "type_item"), + TYPE_EXCEPTION_HANDLER_ITEM( -1, "exception_handler_item"), + TYPE_ANNOTATION_SET_REF_ITEM( -1, "annotation_set_ref_item"); + + /** value when represented in a {@link MapItem} */ + private final int mapValue; + + /** {@code non-null;} name of the type */ + private final String typeName; + + /** {@code non-null;} the short human name */ + private final String humanName; + + /** + * Constructs an instance. + * + * @param mapValue value when represented in a {@link MapItem} + * @param typeName {@code non-null;} name of the type + */ + private ItemType(int mapValue, String typeName) { + this.mapValue = mapValue; + this.typeName = typeName; + + // Make the human name. + String human = typeName; + if (human.endsWith("_item")) { + human = human.substring(0, human.length() - 5); + } + this.humanName = human.replace('_', ' '); + } + + /** + * Gets the map value. + * + * @return the map value + */ + public int getMapValue() { + return mapValue; + } + + /** + * Gets the type name. + * + * @return {@code non-null;} the type name + */ + public String getTypeName() { + return typeName; + } + + /** {@inheritDoc} */ + public String toHuman() { + return humanName; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/MapItem.java b/dexlib/src/main/java/com/android/dx/dex/file/MapItem.java new file mode 100644 index 000000000..f68cd9422 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/MapItem.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.ArrayList; + +/** + * Class that represents a map item. + */ +public final class MapItem extends OffsettedItem { + /** file alignment of this class, in bytes */ + private static final int ALIGNMENT = 4; + + /** write size of this class, in bytes: three {@code uint}s */ + private static final int WRITE_SIZE = (4 * 3); + + /** {@code non-null;} item type this instance covers */ + private final ItemType type; + + /** {@code non-null;} section this instance covers */ + private final Section section; + + /** + * {@code null-ok;} first item covered or {@code null} if this is + * a self-reference + */ + private final Item firstItem; + + /** + * {@code null-ok;} last item covered or {@code null} if this is + * a self-reference + */ + private final Item lastItem; + + /** + * {@code > 0;} count of items covered; {@code 1} if this + * is a self-reference + */ + private final int itemCount; + + /** + * Constructs a list item with instances of this class representing + * the contents of the given array of sections, adding it to the + * given map section. + * + * @param sections {@code non-null;} the sections + * @param mapSection {@code non-null;} the section that the resulting map + * should be added to; it should be empty on entry to this method + */ + public static void addMap(Section[] sections, + MixedItemSection mapSection) { + if (sections == null) { + throw new NullPointerException("sections == null"); + } + + if (mapSection.items().size() != 0) { + throw new IllegalArgumentException( + "mapSection.items().size() != 0"); + } + + ArrayList items = new ArrayList(50); + + for (Section section : sections) { + ItemType currentType = null; + Item firstItem = null; + Item lastItem = null; + int count = 0; + + for (Item item : section.items()) { + ItemType type = item.itemType(); + if (type != currentType) { + if (count != 0) { + items.add(new MapItem(currentType, section, + firstItem, lastItem, count)); + } + currentType = type; + firstItem = item; + count = 0; + } + lastItem = item; + count++; + } + + if (count != 0) { + // Add a MapItem for the final items in the section. + items.add(new MapItem(currentType, section, + firstItem, lastItem, count)); + } else if (section == mapSection) { + // Add a MapItem for the self-referential section. + items.add(new MapItem(mapSection)); + } + } + + mapSection.add( + new UniformListItem(ItemType.TYPE_MAP_LIST, items)); + } + + /** + * Constructs an instance. + * + * @param type {@code non-null;} item type this instance covers + * @param section {@code non-null;} section this instance covers + * @param firstItem {@code non-null;} first item covered + * @param lastItem {@code non-null;} last item covered + * @param itemCount {@code > 0;} count of items covered + */ + private MapItem(ItemType type, Section section, Item firstItem, + Item lastItem, int itemCount) { + super(ALIGNMENT, WRITE_SIZE); + + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (section == null) { + throw new NullPointerException("section == null"); + } + + if (firstItem == null) { + throw new NullPointerException("firstItem == null"); + } + + if (lastItem == null) { + throw new NullPointerException("lastItem == null"); + } + + if (itemCount <= 0) { + throw new IllegalArgumentException("itemCount <= 0"); + } + + this.type = type; + this.section = section; + this.firstItem = firstItem; + this.lastItem = lastItem; + this.itemCount = itemCount; + } + + /** + * Constructs a self-referential instance. This instance is meant to + * represent the section containing the {@code map_list}. + * + * @param section {@code non-null;} section this instance covers + */ + private MapItem(Section section) { + super(ALIGNMENT, WRITE_SIZE); + + if (section == null) { + throw new NullPointerException("section == null"); + } + + this.type = ItemType.TYPE_MAP_LIST; + this.section = section; + this.firstItem = null; + this.lastItem = null; + this.itemCount = 1; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_MAP_ITEM; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(section.toString()); + sb.append(' '); + sb.append(type.toHuman()); + sb.append('}'); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // We have nothing to add. + } + + /** {@inheritDoc} */ + @Override + public final String toHuman() { + return toString(); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + int value = type.getMapValue(); + int offset; + + if (firstItem == null) { + offset = section.getFileOffset(); + } else { + offset = section.getAbsoluteItemOffset(firstItem); + } + + if (out.annotates()) { + out.annotate(0, offsetString() + ' ' + type.getTypeName() + + " map"); + out.annotate(2, " type: " + Hex.u2(value) + " // " + + type.toString()); + out.annotate(2, " unused: 0"); + out.annotate(4, " size: " + Hex.u4(itemCount)); + out.annotate(4, " offset: " + Hex.u4(offset)); + } + + out.writeShort(value); + out.writeShort(0); // unused + out.writeInt(itemCount); + out.writeInt(offset); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/MemberIdItem.java b/dexlib/src/main/java/com/android/dx/dex/file/MemberIdItem.java new file mode 100644 index 000000000..ffe4c6c95 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/MemberIdItem.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.SizeOf; +import com.android.dx.rop.cst.CstMemberRef; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +/** + * Representation of a member (field or method) reference inside a + * Dalvik file. + */ +public abstract class MemberIdItem extends IdItem { + /** {@code non-null;} the constant for the member */ + private final CstMemberRef cst; + + /** + * Constructs an instance. + * + * @param cst {@code non-null;} the constant for the member + */ + public MemberIdItem(CstMemberRef cst) { + super(cst.getDefiningClass()); + + this.cst = cst; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.MEMBER_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + super.addContents(file); + + StringIdsSection stringIds = file.getStringIds(); + stringIds.intern(getRef().getNat().getName()); + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(DexFile file, AnnotatedOutput out) { + TypeIdsSection typeIds = file.getTypeIds(); + StringIdsSection stringIds = file.getStringIds(); + CstNat nat = cst.getNat(); + int classIdx = typeIds.indexOf(getDefiningClass()); + int nameIdx = stringIds.indexOf(nat.getName()); + int typoidIdx = getTypoidIdx(file); + + if (out.annotates()) { + out.annotate(0, indexString() + ' ' + cst.toHuman()); + out.annotate(2, " class_idx: " + Hex.u2(classIdx)); + out.annotate(2, String.format(" %-10s %s", getTypoidName() + ':', + Hex.u2(typoidIdx))); + out.annotate(4, " name_idx: " + Hex.u4(nameIdx)); + } + + out.writeShort(classIdx); + out.writeShort(typoidIdx); + out.writeInt(nameIdx); + } + + /** + * Returns the index of the type-like thing associated with + * this item, in order that it may be written out. Subclasses must + * override this to get whatever it is they need to store. + * + * @param file {@code non-null;} the file being written + * @return the index in question + */ + protected abstract int getTypoidIdx(DexFile file); + + /** + * Returns the field name of the type-like thing associated with + * this item, for listing-generating purposes. Subclasses must override + * this. + * + * @return {@code non-null;} the name in question + */ + protected abstract String getTypoidName(); + + /** + * Gets the member constant. + * + * @return {@code non-null;} the constant + */ + public final CstMemberRef getRef() { + return cst; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/MemberIdsSection.java b/dexlib/src/main/java/com/android/dx/dex/file/MemberIdsSection.java new file mode 100644 index 000000000..9fdada546 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/MemberIdsSection.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.DexFormat; +import com.android.dex.DexIndexOverflowException; + +import java.util.Formatter; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Member (field or method) refs list section of a {@code .dex} file. + */ +public abstract class MemberIdsSection extends UniformItemSection { + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + */ + public MemberIdsSection(String name, DexFile file) { + super(name, file, 4); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + if (items().size() > DexFormat.MAX_MEMBER_IDX + 1) { + throw new DexIndexOverflowException(getTooManyMembersMessage()); + } + + for (Object i : items()) { + ((MemberIdItem) i).setIndex(idx); + idx++; + } + } + + private String getTooManyMembersMessage() { + Map membersByPackage = new TreeMap(); + for (Object member : items()) { + String packageName = ((MemberIdItem) member).getDefiningClass().getPackageName(); + AtomicInteger count = membersByPackage.get(packageName); + if (count == null) { + count = new AtomicInteger(); + membersByPackage.put(packageName, count); + } + count.incrementAndGet(); + } + + Formatter formatter = new Formatter(); + try { + String memberType = this instanceof MethodIdsSection ? "method" : "field"; + formatter.format("Too many %1$s references to fit in one dex file: %2$d; max is %3$d.%n" + + "You may try using multi-dex. If multi-dex is enabled then the list of " + + "classes for the main dex list is too large.%n" + + "References by package:", + memberType, items().size(), DexFormat.MAX_MEMBER_IDX + 1); + for (Map.Entry entry : membersByPackage.entrySet()) { + formatter.format("%n%6d %s", entry.getValue().get(), entry.getKey()); + } + return formatter.toString(); + } finally { + formatter.close(); + } + } + +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/MethodAnnotationStruct.java b/dexlib/src/main/java/com/android/dx/dex/file/MethodAnnotationStruct.java new file mode 100644 index 000000000..38f7ce44b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/MethodAnnotationStruct.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import com.android.dx.util.ToHuman; + +/** + * Association of a method and its annotations. + */ +public final class MethodAnnotationStruct + implements ToHuman, Comparable { + /** {@code non-null;} the method in question */ + private final CstMethodRef method; + + /** {@code non-null;} the associated annotations */ + private AnnotationSetItem annotations; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method in question + * @param annotations {@code non-null;} the associated annotations + */ + public MethodAnnotationStruct(CstMethodRef method, + AnnotationSetItem annotations) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + this.method = method; + this.annotations = annotations; + } + + /** {@inheritDoc} */ + public int hashCode() { + return method.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof MethodAnnotationStruct)) { + return false; + } + + return method.equals(((MethodAnnotationStruct) other).method); + } + + /** {@inheritDoc} */ + public int compareTo(MethodAnnotationStruct other) { + return method.compareTo(other.method); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MethodIdsSection methodIds = file.getMethodIds(); + MixedItemSection wordData = file.getWordData(); + + methodIds.intern(method); + annotations = wordData.intern(annotations); + } + + /** {@inheritDoc} */ + public void writeTo(DexFile file, AnnotatedOutput out) { + int methodIdx = file.getMethodIds().indexOf(method); + int annotationsOff = annotations.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, " " + method.toHuman()); + out.annotate(4, " method_idx: " + Hex.u4(methodIdx)); + out.annotate(4, " annotations_off: " + + Hex.u4(annotationsOff)); + } + + out.writeInt(methodIdx); + out.writeInt(annotationsOff); + } + + /** {@inheritDoc} */ + public String toHuman() { + return method.toHuman() + ": " + annotations; + } + + /** + * Gets the method this item is for. + * + * @return {@code non-null;} the method + */ + public CstMethodRef getMethod() { + return method; + } + + /** + * Gets the associated annotations. + * + * @return {@code non-null;} the annotations + */ + public Annotations getAnnotations() { + return annotations.getAnnotations(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/MethodIdItem.java b/dexlib/src/main/java/com/android/dx/dex/file/MethodIdItem.java new file mode 100644 index 000000000..f2ff4f99d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/MethodIdItem.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.CstBaseMethodRef; + +/** + * Representation of a method reference inside a Dalvik file. + */ +public final class MethodIdItem extends MemberIdItem { + /** + * Constructs an instance. + * + * @param method {@code non-null;} the constant for the method + */ + public MethodIdItem(CstBaseMethodRef method) { + super(method); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_METHOD_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + super.addContents(file); + + ProtoIdsSection protoIds = file.getProtoIds(); + protoIds.intern(getMethodRef().getPrototype()); + } + + /** + * Gets the method constant. + * + * @return {@code non-null;} the constant + */ + public CstBaseMethodRef getMethodRef() { + return (CstBaseMethodRef) getRef(); + } + + /** {@inheritDoc} */ + @Override + protected int getTypoidIdx(DexFile file) { + ProtoIdsSection protoIds = file.getProtoIds(); + return protoIds.indexOf(getMethodRef().getPrototype()); + } + + /** {@inheritDoc} */ + @Override + protected String getTypoidName() { + return "proto_idx"; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/MethodIdsSection.java b/dexlib/src/main/java/com/android/dx/dex/file/MethodIdsSection.java new file mode 100644 index 000000000..254d7fe43 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/MethodIdsSection.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstBaseMethodRef; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Method refs list section of a {@code .dex} file. + */ +public final class MethodIdsSection extends MemberIdsSection { + /** + * {@code non-null;} map from method constants to {@link + * MethodIdItem} instances + */ + private final TreeMap methodIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public MethodIdsSection(DexFile file) { + super("method_ids", file); + + methodIds = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return methodIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + IndexedItem result = methodIds.get((CstBaseMethodRef) cst); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = methodIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "method_ids_size: " + Hex.u4(sz)); + out.annotate(4, "method_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param method {@code non-null;} the reference to intern + * @return {@code non-null;} the interned reference + */ + public synchronized MethodIdItem intern(CstBaseMethodRef method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + throwIfPrepared(); + + MethodIdItem result = methodIds.get(method); + + if (result == null) { + result = new MethodIdItem(method); + methodIds.put(method, result); + } + + return result; + } + + /** + * Gets the index of the given reference, which must have been added + * to this instance. + * + * @param ref {@code non-null;} the reference to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(CstBaseMethodRef ref) { + if (ref == null) { + throw new NullPointerException("ref == null"); + } + + throwIfNotPrepared(); + + MethodIdItem item = methodIds.get(ref); + + if (item == null) { + throw new IllegalArgumentException("not found"); + } + + return item.getIndex(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/MixedItemSection.java b/dexlib/src/main/java/com/android/dx/dex/file/MixedItemSection.java new file mode 100644 index 000000000..9053043f9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/MixedItemSection.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.TreeMap; + +/** + * A section of a {@code .dex} file which consists of a sequence of + * {@link OffsettedItem} objects, which may each be of a different concrete + * class and/or size. + * + * Note: It is invalid for an item in an instance of this class to + * have a larger alignment requirement than the alignment of this instance. + */ +public final class MixedItemSection extends Section { + static enum SortType { + /** no sorting */ + NONE, + + /** sort by type only */ + TYPE, + + /** sort in class-major order, with instances sorted per-class */ + INSTANCE; + }; + + /** {@code non-null;} sorter which sorts instances by type */ + private static final Comparator TYPE_SORTER = + new Comparator() { + public int compare(OffsettedItem item1, OffsettedItem item2) { + ItemType type1 = item1.itemType(); + ItemType type2 = item2.itemType(); + return type1.compareTo(type2); + } + }; + + /** {@code non-null;} the items in this part */ + private final ArrayList items; + + /** {@code non-null;} items that have been explicitly interned */ + private final HashMap interns; + + /** {@code non-null;} how to sort the items */ + private final SortType sort; + + /** + * {@code >= -1;} the current size of this part, in bytes, or {@code -1} + * if not yet calculated + */ + private int writeSize; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + * @param alignment {@code > 0;} alignment requirement for the final output; + * must be a power of 2 + * @param sort how the items should be sorted in the final output + */ + public MixedItemSection(String name, DexFile file, int alignment, + SortType sort) { + super(name, file, alignment); + + this.items = new ArrayList(100); + this.interns = new HashMap(100); + this.sort = sort; + this.writeSize = -1; + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return items; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + throwIfNotPrepared(); + return writeSize; + } + + /** {@inheritDoc} */ + @Override + public int getAbsoluteItemOffset(Item item) { + OffsettedItem oi = (OffsettedItem) item; + return oi.getAbsoluteOffset(); + } + + /** + * Gets the size of this instance, in items. + * + * @return {@code >= 0;} the size + */ + public int size() { + return items.size(); + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + if (writeSize == -1) { + throw new RuntimeException("write size not yet set"); + } + + int sz = writeSize; + int offset = (sz == 0) ? 0 : getFileOffset(); + String name = getName(); + + if (name == null) { + name = ""; + } + + int spaceCount = 15 - name.length(); + char[] spaceArr = new char[spaceCount]; + Arrays.fill(spaceArr, ' '); + String spaces = new String(spaceArr); + + if (out.annotates()) { + out.annotate(4, name + "_size:" + spaces + Hex.u4(sz)); + out.annotate(4, name + "_off: " + spaces + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Adds an item to this instance. This will in turn tell the given item + * that it has been added to this instance. It is invalid to add the + * same item to more than one instance, nor to add the same items + * multiple times to a single instance. + * + * @param item {@code non-null;} the item to add + */ + public void add(OffsettedItem item) { + throwIfPrepared(); + + try { + if (item.getAlignment() > getAlignment()) { + throw new IllegalArgumentException( + "incompatible item alignment"); + } + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("item == null"); + } + + items.add(item); + } + + /** + * Interns an item in this instance, returning the interned instance + * (which may not be the one passed in). This will add the item if no + * equal item has been added. + * + * @param item {@code non-null;} the item to intern + * @return {@code non-null;} the equivalent interned instance + */ + public synchronized T intern(T item) { + throwIfPrepared(); + + OffsettedItem result = interns.get(item); + + if (result != null) { + return (T) result; + } + + add(item); + interns.put(item, item); + return item; + } + + /** + * Gets an item which was previously interned. + * + * @param item {@code non-null;} the item to look for + * @return {@code non-null;} the equivalent already-interned instance + */ + public T get(T item) { + throwIfNotPrepared(); + + OffsettedItem result = interns.get(item); + + if (result != null) { + return (T) result; + } + + throw new NoSuchElementException(item.toString()); + } + + /** + * Writes an index of contents of the items in this instance of the + * given type. If there are none, this writes nothing. If there are any, + * then the index is preceded by the given intro string. + * + * @param out {@code non-null;} where to write to + * @param itemType {@code non-null;} the item type of interest + * @param intro {@code non-null;} the introductory string for non-empty indices + */ + public void writeIndexAnnotation(AnnotatedOutput out, ItemType itemType, + String intro) { + throwIfNotPrepared(); + + TreeMap index = + new TreeMap(); + + for (OffsettedItem item : items) { + if (item.itemType() == itemType) { + String label = item.toHuman(); + index.put(label, item); + } + } + + if (index.size() == 0) { + return; + } + + out.annotate(0, intro); + + for (Map.Entry entry : index.entrySet()) { + String label = entry.getKey(); + OffsettedItem item = entry.getValue(); + out.annotate(0, item.offsetString() + ' ' + label + '\n'); + } + } + + /** {@inheritDoc} */ + @Override + protected void prepare0() { + DexFile file = getFile(); + + /* + * It's okay for new items to be added as a result of an + * addContents() call; we just have to deal with the possibility. + */ + + int i = 0; + for (;;) { + int sz = items.size(); + if (i >= sz) { + break; + } + + for (/*i*/; i < sz; i++) { + OffsettedItem one = items.get(i); + one.addContents(file); + } + } + } + + /** + * Places all the items in this instance at particular offsets. This + * will call {@link OffsettedItem#place} on each item. If an item + * does not know its write size before the call to {@code place}, + * it is that call which is responsible for setting the write size. + * This method may only be called once per instance; subsequent calls + * will throw an exception. + */ + public void placeItems() { + throwIfNotPrepared(); + + switch (sort) { + case INSTANCE: { + Collections.sort(items); + break; + } + case TYPE: { + Collections.sort(items, TYPE_SORTER); + break; + } + } + + int sz = items.size(); + int outAt = 0; + for (int i = 0; i < sz; i++) { + OffsettedItem one = items.get(i); + try { + int placedAt = one.place(this, outAt); + + if (placedAt < outAt) { + throw new RuntimeException("bogus place() result for " + + one); + } + + outAt = placedAt + one.writeSize(); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while placing " + one); + } + } + + writeSize = outAt; + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(AnnotatedOutput out) { + boolean annotates = out.annotates(); + boolean first = true; + DexFile file = getFile(); + int at = 0; + + for (OffsettedItem one : items) { + if (annotates) { + if (first) { + first = false; + } else { + out.annotate(0, "\n"); + } + } + + int alignMask = one.getAlignment() - 1; + int writeAt = (at + alignMask) & ~alignMask; + + if (at != writeAt) { + out.writeZeroes(writeAt - at); + at = writeAt; + } + + one.writeTo(file, out); + at += one.writeSize(); + } + + if (at != writeSize) { + throw new RuntimeException("output size mismatch"); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/OffsettedItem.java b/dexlib/src/main/java/com/android/dx/dex/file/OffsettedItem.java new file mode 100644 index 000000000..02787f90f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/OffsettedItem.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.util.AnnotatedOutput; + +/** + * An item in a Dalvik file which is referenced by absolute offset. + */ +public abstract class OffsettedItem extends Item + implements Comparable { + /** {@code > 0;} alignment requirement */ + private final int alignment; + + /** {@code >= -1;} the size of this instance when written, in bytes, or + * {@code -1} if not yet known */ + private int writeSize; + + /** + * {@code null-ok;} section the item was added to, or {@code null} if + * not yet added + */ + private Section addedTo; + + /** + * {@code >= -1;} assigned offset of the item from the start of its section, + * or {@code -1} if not yet assigned + */ + private int offset; + + /** + * Gets the absolute offset of the given item, returning {@code 0} + * if handed {@code null}. + * + * @param item {@code null-ok;} the item in question + * @return {@code >= 0;} the item's absolute offset, or {@code 0} + * if {@code item == null} + */ + public static int getAbsoluteOffsetOr0(OffsettedItem item) { + if (item == null) { + return 0; + } + + return item.getAbsoluteOffset(); + } + + /** + * Constructs an instance. The offset is initially unassigned. + * + * @param alignment {@code > 0;} output alignment requirement; must be a + * power of 2 + * @param writeSize {@code >= -1;} the size of this instance when written, + * in bytes, or {@code -1} if not immediately known + */ + public OffsettedItem(int alignment, int writeSize) { + Section.validateAlignment(alignment); + + if (writeSize < -1) { + throw new IllegalArgumentException("writeSize < -1"); + } + + this.alignment = alignment; + this.writeSize = writeSize; + this.addedTo = null; + this.offset = -1; + } + + /** + * {@inheritDoc} + * + * Comparisons for this class are defined to be type-major (if the + * types don't match then the objects are not equal), with + * {@link #compareTo0} deciding same-type comparisons. + */ + @Override + public final boolean equals(Object other) { + if (this == other) { + return true; + } + + OffsettedItem otherItem = (OffsettedItem) other; + ItemType thisType = itemType(); + ItemType otherType = otherItem.itemType(); + + if (thisType != otherType) { + return false; + } + + return (compareTo0(otherItem) == 0); + } + + /** + * {@inheritDoc} + * + * Comparisons for this class are defined to be class-major (if the + * classes don't match then the objects are not equal), with + * {@link #compareTo0} deciding same-class comparisons. + */ + public final int compareTo(OffsettedItem other) { + if (this == other) { + return 0; + } + + ItemType thisType = itemType(); + ItemType otherType = other.itemType(); + + if (thisType != otherType) { + return thisType.compareTo(otherType); + } + + return compareTo0(other); + } + + /** + * Sets the write size of this item. This may only be called once + * per instance, and only if the size was unknown upon instance + * creation. + * + * @param writeSize {@code > 0;} the write size, in bytes + */ + public final void setWriteSize(int writeSize) { + if (writeSize < 0) { + throw new IllegalArgumentException("writeSize < 0"); + } + + if (this.writeSize >= 0) { + throw new UnsupportedOperationException("writeSize already set"); + } + + this.writeSize = writeSize; + } + + /** {@inheritDoc} + * + * @throws UnsupportedOperationException thrown if the write size + * is not yet known + */ + @Override + public final int writeSize() { + if (writeSize < 0) { + throw new UnsupportedOperationException("writeSize is unknown"); + } + + return writeSize; + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(DexFile file, AnnotatedOutput out) { + out.alignTo(alignment); + + try { + if (writeSize < 0) { + throw new UnsupportedOperationException( + "writeSize is unknown"); + } + out.assertCursor(getAbsoluteOffset()); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing " + this); + } + + writeTo0(file, out); + } + + /** + * Gets the relative item offset. The offset is from the start of + * the section which the instance was written to. + * + * @return {@code >= 0;} the offset + * @throws RuntimeException thrown if the offset is not yet known + */ + public final int getRelativeOffset() { + if (offset < 0) { + throw new RuntimeException("offset not yet known"); + } + + return offset; + } + + /** + * Gets the absolute item offset. The offset is from the start of + * the file which the instance was written to. + * + * @return {@code >= 0;} the offset + * @throws RuntimeException thrown if the offset is not yet known + */ + public final int getAbsoluteOffset() { + if (offset < 0) { + throw new RuntimeException("offset not yet known"); + } + + return addedTo.getAbsoluteOffset(offset); + } + + /** + * Indicates that this item has been added to the given section at + * the given offset. It is only valid to call this method once per + * instance. + * + * @param addedTo {@code non-null;} the section this instance has + * been added to + * @param offset {@code >= 0;} the desired offset from the start of the + * section where this instance was placed + * @return {@code >= 0;} the offset that this instance should be placed at + * in order to meet its alignment constraint + */ + public final int place(Section addedTo, int offset) { + if (addedTo == null) { + throw new NullPointerException("addedTo == null"); + } + + if (offset < 0) { + throw new IllegalArgumentException("offset < 0"); + } + + if (this.addedTo != null) { + throw new RuntimeException("already written"); + } + + int mask = alignment - 1; + offset = (offset + mask) & ~mask; + + this.addedTo = addedTo; + this.offset = offset; + + place0(addedTo, offset); + + return offset; + } + + /** + * Gets the alignment requirement of this instance. An instance should + * only be written when so aligned. + * + * @return {@code > 0;} the alignment requirement; must be a power of 2 + */ + public final int getAlignment() { + return alignment; + } + + /** + * Gets the absolute offset of this item as a string, suitable for + * including in annotations. + * + * @return {@code non-null;} the offset string + */ + public final String offsetString() { + return '[' + Integer.toHexString(getAbsoluteOffset()) + ']'; + } + + /** + * Gets a short human-readable string representing this instance. + * + * @return {@code non-null;} the human form + */ + public abstract String toHuman(); + + /** + * Compares this instance to another which is guaranteed to be of + * the same class. The default implementation of this method is to + * throw an exception (unsupported operation). If a particular + * class needs to actually sort, then it should override this + * method. + * + * @param other {@code non-null;} instance to compare to + * @return {@code -1}, {@code 0}, or {@code 1}, depending + * on the sort order of this instance and the other + */ + protected int compareTo0(OffsettedItem other) { + throw new UnsupportedOperationException("unsupported"); + } + + /** + * Does additional work required when placing an instance. The + * default implementation of this method is a no-op. If a + * particular class needs to do something special, then it should + * override this method. In particular, if this instance did not + * know its write size up-front, then this method is responsible + * for setting it. + * + * @param addedTo {@code non-null;} the section this instance has been added to + * @param offset {@code >= 0;} the offset from the start of the + * section where this instance was placed + */ + protected void place0(Section addedTo, int offset) { + // This space intentionally left blank. + } + + /** + * Performs the actual write of the contents of this instance to + * the given data section. This is called by {@link #writeTo}, + * which will have taken care of ensuring alignment. + * + * @param file {@code non-null;} the file to use for reference + * @param out {@code non-null;} where to write to + */ + protected abstract void writeTo0(DexFile file, AnnotatedOutput out); +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/ParameterAnnotationStruct.java b/dexlib/src/main/java/com/android/dx/dex/file/ParameterAnnotationStruct.java new file mode 100644 index 000000000..0fe92ab6f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/ParameterAnnotationStruct.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.annotation.Annotations; +import com.android.dx.rop.annotation.AnnotationsList; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import com.android.dx.util.ToHuman; + +import java.util.ArrayList; + +/** + * Association of a method and its parameter annotations. + */ +public final class ParameterAnnotationStruct + implements ToHuman, Comparable { + /** {@code non-null;} the method in question */ + private final CstMethodRef method; + + /** {@code non-null;} the associated annotations list */ + private final AnnotationsList annotationsList; + + /** {@code non-null;} the associated annotations list, as an item */ + private final UniformListItem annotationsItem; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method in question + * @param annotationsList {@code non-null;} the associated annotations list + * @param dexFile {@code non-null;} dex output + */ + public ParameterAnnotationStruct(CstMethodRef method, + AnnotationsList annotationsList, DexFile dexFile) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (annotationsList == null) { + throw new NullPointerException("annotationsList == null"); + } + + this.method = method; + this.annotationsList = annotationsList; + + /* + * Construct an item for the annotations list. TODO: This + * requires way too much copying; fix it. + */ + + int size = annotationsList.size(); + ArrayList arrayList = new + ArrayList(size); + + for (int i = 0; i < size; i++) { + Annotations annotations = annotationsList.get(i); + AnnotationSetItem item = new AnnotationSetItem(annotations, dexFile); + arrayList.add(new AnnotationSetRefItem(item)); + } + + this.annotationsItem = new UniformListItem( + ItemType.TYPE_ANNOTATION_SET_REF_LIST, arrayList); + } + + /** {@inheritDoc} */ + public int hashCode() { + return method.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof ParameterAnnotationStruct)) { + return false; + } + + return method.equals(((ParameterAnnotationStruct) other).method); + } + + /** {@inheritDoc} */ + public int compareTo(ParameterAnnotationStruct other) { + return method.compareTo(other.method); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MethodIdsSection methodIds = file.getMethodIds(); + MixedItemSection wordData = file.getWordData(); + + methodIds.intern(method); + wordData.add(annotationsItem); + } + + /** {@inheritDoc} */ + public void writeTo(DexFile file, AnnotatedOutput out) { + int methodIdx = file.getMethodIds().indexOf(method); + int annotationsOff = annotationsItem.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, " " + method.toHuman()); + out.annotate(4, " method_idx: " + Hex.u4(methodIdx)); + out.annotate(4, " annotations_off: " + + Hex.u4(annotationsOff)); + } + + out.writeInt(methodIdx); + out.writeInt(annotationsOff); + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append(method.toHuman()); + sb.append(": "); + + boolean first = true; + for (AnnotationSetRefItem item : annotationsItem.getItems()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(item.toHuman()); + } + + return sb.toString(); + } + + /** + * Gets the method this item is for. + * + * @return {@code non-null;} the method + */ + public CstMethodRef getMethod() { + return method; + } + + /** + * Gets the associated annotations list. + * + * @return {@code non-null;} the annotations list + */ + public AnnotationsList getAnnotationsList() { + return annotationsList; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/ProtoIdItem.java b/dexlib/src/main/java/com/android/dx/dex/file/ProtoIdItem.java new file mode 100644 index 000000000..19eb3d27d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/ProtoIdItem.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.SizeOf; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +/** + * Representation of a method prototype reference inside a Dalvik file. + */ +public final class ProtoIdItem extends IndexedItem { + /** {@code non-null;} the wrapped prototype */ + private final Prototype prototype; + + /** {@code non-null;} the short-form of the prototype */ + private final CstString shortForm; + + /** + * {@code null-ok;} the list of parameter types or {@code null} if this + * prototype has no parameters + */ + private TypeListItem parameterTypes; + + /** + * Constructs an instance. + * + * @param prototype {@code non-null;} the constant for the prototype + */ + public ProtoIdItem(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + this.prototype = prototype; + this.shortForm = makeShortForm(prototype); + + StdTypeList parameters = prototype.getParameterTypes(); + this.parameterTypes = (parameters.size() == 0) ? null + : new TypeListItem(parameters); + } + + /** + * Creates the short-form of the given prototype. + * + * @param prototype {@code non-null;} the prototype + * @return {@code non-null;} the short form + */ + private static CstString makeShortForm(Prototype prototype) { + StdTypeList parameters = prototype.getParameterTypes(); + int size = parameters.size(); + StringBuilder sb = new StringBuilder(size + 1); + + sb.append(shortFormCharFor(prototype.getReturnType())); + + for (int i = 0; i < size; i++) { + sb.append(shortFormCharFor(parameters.getType(i))); + } + + return new CstString(sb.toString()); + } + + /** + * Gets the short-form character for the given type. + * + * @param type {@code non-null;} the type + * @return the corresponding short-form character + */ + private static char shortFormCharFor(Type type) { + char descriptorChar = type.getDescriptor().charAt(0); + + if (descriptorChar == '[') { + return 'L'; + } + + return descriptorChar; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_PROTO_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.PROTO_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + StringIdsSection stringIds = file.getStringIds(); + TypeIdsSection typeIds = file.getTypeIds(); + MixedItemSection typeLists = file.getTypeLists(); + + typeIds.intern(prototype.getReturnType()); + stringIds.intern(shortForm); + + if (parameterTypes != null) { + parameterTypes = typeLists.intern(parameterTypes); + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + int shortyIdx = file.getStringIds().indexOf(shortForm); + int returnIdx = file.getTypeIds().indexOf(prototype.getReturnType()); + int paramsOff = OffsettedItem.getAbsoluteOffsetOr0(parameterTypes); + + if (out.annotates()) { + StringBuilder sb = new StringBuilder(); + sb.append(prototype.getReturnType().toHuman()); + sb.append(" proto("); + + StdTypeList params = prototype.getParameterTypes(); + int size = params.size(); + + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(params.getType(i).toHuman()); + } + + sb.append(")"); + out.annotate(0, indexString() + ' ' + sb.toString()); + out.annotate(4, " shorty_idx: " + Hex.u4(shortyIdx) + + " // " + shortForm.toQuoted()); + out.annotate(4, " return_type_idx: " + Hex.u4(returnIdx) + + " // " + prototype.getReturnType().toHuman()); + out.annotate(4, " parameters_off: " + Hex.u4(paramsOff)); + } + + out.writeInt(shortyIdx); + out.writeInt(returnIdx); + out.writeInt(paramsOff); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/ProtoIdsSection.java b/dexlib/src/main/java/com/android/dx/dex/file/ProtoIdsSection.java new file mode 100644 index 000000000..41be5e923 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/ProtoIdsSection.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstProtoRef; +import com.android.dx.rop.type.Prototype; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.Collection; +import java.util.TreeMap; + +/** + * Proto (method prototype) identifiers list section of a + * {@code .dex} file. + */ +public final class ProtoIdsSection extends UniformItemSection { + /** + * {@code non-null;} map from method prototypes to {@link ProtoIdItem} instances + */ + private final TreeMap protoIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public ProtoIdsSection(DexFile file) { + super("proto_ids", file, 4); + + protoIds = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return protoIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + if (!(cst instanceof CstProtoRef)) { + throw new IllegalArgumentException("cst not instance of CstProtoRef"); + } + + throwIfNotPrepared(); + CstProtoRef protoRef = (CstProtoRef) cst; + IndexedItem result = protoIds.get(protoRef.getPrototype()); + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = protoIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (sz > 65536) { + throw new UnsupportedOperationException("too many proto ids"); + } + + if (out.annotates()) { + out.annotate(4, "proto_ids_size: " + Hex.u4(sz)); + out.annotate(4, "proto_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param prototype {@code non-null;} the prototype to intern + * @return {@code non-null;} the interned reference + */ + public synchronized ProtoIdItem intern(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + throwIfPrepared(); + + ProtoIdItem result = protoIds.get(prototype); + + if (result == null) { + result = new ProtoIdItem(prototype); + protoIds.put(prototype, result); + } + + return result; + } + + /** + * Gets the index of the given prototype, which must have + * been added to this instance. + * + * @param prototype {@code non-null;} the prototype to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + throwIfNotPrepared(); + + ProtoIdItem item = protoIds.get(prototype); + + if (item == null) { + throw new IllegalArgumentException("not found"); + } + + return item.getIndex(); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (Object i : items()) { + ((ProtoIdItem) i).setIndex(idx); + idx++; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/Section.java b/dexlib/src/main/java/com/android/dx/dex/file/Section.java new file mode 100644 index 000000000..bde714c46 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/Section.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.util.AnnotatedOutput; +import java.util.Collection; + +/** + * A section of a {@code .dex} file. Each section consists of a list + * of items of some sort or other. + */ +public abstract class Section { + /** {@code null-ok;} name of this part, for annotation purposes */ + private final String name; + + /** {@code non-null;} file that this instance is part of */ + private final DexFile file; + + /** {@code > 0;} alignment requirement for the final output; + * must be a power of 2 */ + private final int alignment; + + /** {@code >= -1;} offset from the start of the file to this part, or + * {@code -1} if not yet known */ + private int fileOffset; + + /** whether {@link #prepare} has been called successfully on this + * instance */ + private boolean prepared; + + /** + * Validates an alignment. + * + * @param alignment the alignment + * @throws IllegalArgumentException thrown if {@code alignment} + * isn't a positive power of 2 + */ + public static void validateAlignment(int alignment) { + if ((alignment <= 0) || + (alignment & (alignment - 1)) != 0) { + throw new IllegalArgumentException("invalid alignment"); + } + } + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + * @param alignment {@code > 0;} alignment requirement for the final output; + * must be a power of 2 + */ + public Section(String name, DexFile file, int alignment) { + if (file == null) { + throw new NullPointerException("file == null"); + } + + validateAlignment(alignment); + + this.name = name; + this.file = file; + this.alignment = alignment; + this.fileOffset = -1; + this.prepared = false; + } + + /** + * Gets the file that this instance is part of. + * + * @return {@code non-null;} the file + */ + public final DexFile getFile() { + return file; + } + + /** + * Gets the alignment for this instance's final output. + * + * @return {@code > 0;} the alignment + */ + public final int getAlignment() { + return alignment; + } + + /** + * Gets the offset from the start of the file to this part. This + * throws an exception if the offset has not yet been set. + * + * @return {@code >= 0;} the file offset + */ + public final int getFileOffset() { + if (fileOffset < 0) { + throw new RuntimeException("fileOffset not set"); + } + + return fileOffset; + } + + /** + * Sets the file offset. It is only valid to call this method once + * once per instance. + * + * @param fileOffset {@code >= 0;} the desired offset from the start of the + * file where this for this instance + * @return {@code >= 0;} the offset that this instance should be placed at + * in order to meet its alignment constraint + */ + public final int setFileOffset(int fileOffset) { + if (fileOffset < 0) { + throw new IllegalArgumentException("fileOffset < 0"); + } + + if (this.fileOffset >= 0) { + throw new RuntimeException("fileOffset already set"); + } + + int mask = alignment - 1; + fileOffset = (fileOffset + mask) & ~mask; + + this.fileOffset = fileOffset; + + return fileOffset; + } + + /** + * Writes this instance to the given raw data object. + * + * @param out {@code non-null;} where to write to + */ + public final void writeTo(AnnotatedOutput out) { + throwIfNotPrepared(); + align(out); + + int cursor = out.getCursor(); + + if (fileOffset < 0) { + fileOffset = cursor; + } else if (fileOffset != cursor) { + throw new RuntimeException("alignment mismatch: for " + this + + ", at " + cursor + + ", but expected " + fileOffset); + } + + if (out.annotates()) { + if (name != null) { + out.annotate(0, "\n" + name + ":"); + } else if (cursor != 0) { + out.annotate(0, "\n"); + } + } + + writeTo0(out); + } + + /** + * Returns the absolute file offset, given an offset from the + * start of this instance's output. This is only valid to call + * once this instance has been assigned a file offset (via {@link + * #setFileOffset}). + * + * @param relative {@code >= 0;} the relative offset + * @return {@code >= 0;} the corresponding absolute file offset + */ + public final int getAbsoluteOffset(int relative) { + if (relative < 0) { + throw new IllegalArgumentException("relative < 0"); + } + + if (fileOffset < 0) { + throw new RuntimeException("fileOffset not yet set"); + } + + return fileOffset + relative; + } + + /** + * Returns the absolute file offset of the given item which must + * be contained in this section. This is only valid to call + * once this instance has been assigned a file offset (via {@link + * #setFileOffset}). + * + *

Note: Subclasses must implement this as appropriate for + * their contents.

+ * + * @param item {@code non-null;} the item in question + * @return {@code >= 0;} the item's absolute file offset + */ + public abstract int getAbsoluteItemOffset(Item item); + + /** + * Prepares this instance for writing. This performs any necessary + * prerequisites, including particularly adding stuff to other + * sections. This method may only be called once per instance; + * subsequent calls will throw an exception. + */ + public final void prepare() { + throwIfPrepared(); + prepare0(); + prepared = true; + } + + /** + * Gets the collection of all the items in this section. + * It is not valid to attempt to change the returned list. + * + * @return {@code non-null;} the items + */ + public abstract Collection items(); + + /** + * Does the main work of {@link #prepare}. + */ + protected abstract void prepare0(); + + /** + * Gets the size of this instance when output, in bytes. + * + * @return {@code >= 0;} the size of this instance, in bytes + */ + public abstract int writeSize(); + + /** + * Throws an exception if {@link #prepare} has not been + * called on this instance. + */ + protected final void throwIfNotPrepared() { + if (!prepared) { + throw new RuntimeException("not prepared"); + } + } + + /** + * Throws an exception if {@link #prepare} has already been called + * on this instance. + */ + protected final void throwIfPrepared() { + if (prepared) { + throw new RuntimeException("already prepared"); + } + } + + /** + * Aligns the output of the given data to the alignment of this instance. + * + * @param out {@code non-null;} the output to align + */ + protected final void align(AnnotatedOutput out) { + out.alignTo(alignment); + } + + /** + * Writes this instance to the given raw data object. This gets + * called by {@link #writeTo} after aligning the cursor of + * {@code out} and verifying that either the assigned file + * offset matches the actual cursor {@code out} or that the + * file offset was not previously assigned, in which case it gets + * assigned to {@code out}'s cursor. + * + * @param out {@code non-null;} where to write to + */ + protected abstract void writeTo0(AnnotatedOutput out); + + /** + * Returns the name of this section, for annotation purposes. + * + * @return {@code null-ok;} name of this part, for annotation purposes + */ + protected final String getName() { + return name; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/Statistics.java b/dexlib/src/main/java/com/android/dx/dex/file/Statistics.java new file mode 100644 index 000000000..42f5b490e --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/Statistics.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.util.AnnotatedOutput; +import java.util.Collection; +import java.util.HashMap; +import java.util.TreeMap; + +/** + * Statistics about the contents of a file. + */ +public final class Statistics { + /** {@code non-null;} data about each type of item */ + private final HashMap dataMap; + + /** + * Constructs an instance. + */ + public Statistics() { + dataMap = new HashMap(50); + } + + /** + * Adds the given item to the statistics. + * + * @param item {@code non-null;} the item to add + */ + public void add(Item item) { + String typeName = item.typeName(); + Data data = dataMap.get(typeName); + + if (data == null) { + dataMap.put(typeName, new Data(item, typeName)); + } else { + data.add(item); + } + } + + /** + * Adds the given list of items to the statistics. + * + * @param list {@code non-null;} the list of items to add + */ + public void addAll(Section list) { + Collection items = list.items(); + for (Item item : items) { + add(item); + } + } + + /** + * Writes the statistics as an annotation. + * + * @param out {@code non-null;} where to write to + */ + public final void writeAnnotation(AnnotatedOutput out) { + if (dataMap.size() == 0) { + return; + } + + out.annotate(0, "\nstatistics:\n"); + + TreeMap sortedData = new TreeMap(); + + for (Data data : dataMap.values()) { + sortedData.put(data.name, data); + } + + for (Data data : sortedData.values()) { + data.writeAnnotation(out); + } + } + + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append("Statistics:\n"); + + TreeMap sortedData = new TreeMap(); + + for (Data data : dataMap.values()) { + sortedData.put(data.name, data); + } + + for (Data data : sortedData.values()) { + sb.append(data.toHuman()); + } + + return sb.toString(); + } + + /** + * Statistical data about a particular class. + */ + private static class Data { + /** {@code non-null;} name to use as a label */ + private final String name; + + /** {@code >= 0;} number of instances */ + private int count; + + /** {@code >= 0;} total size of instances in bytes */ + private int totalSize; + + /** {@code >= 0;} largest size of any individual item */ + private int largestSize; + + /** {@code >= 0;} smallest size of any individual item */ + private int smallestSize; + + /** + * Constructs an instance for the given item. + * + * @param item {@code non-null;} item in question + * @param name {@code non-null;} type name to use + */ + public Data(Item item, String name) { + int size = item.writeSize(); + + this.name = name; + this.count = 1; + this.totalSize = size; + this.largestSize = size; + this.smallestSize = size; + } + + /** + * Incorporates a new item. This assumes the type name matches. + * + * @param item {@code non-null;} item to incorporate + */ + public void add(Item item) { + int size = item.writeSize(); + + count++; + totalSize += size; + + if (size > largestSize) { + largestSize = size; + } + + if (size < smallestSize) { + smallestSize = size; + } + } + + /** + * Writes this instance as an annotation. + * + * @param out {@code non-null;} where to write to + */ + public void writeAnnotation(AnnotatedOutput out) { + out.annotate(toHuman()); + } + + /** + * Generates a human-readable string for this data item. + * + * @return string for human consumption. + */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append(" " + name + ": " + + count + " item" + (count == 1 ? "" : "s") + "; " + + totalSize + " bytes total\n"); + + if (smallestSize == largestSize) { + sb.append(" " + smallestSize + " bytes/item\n"); + } else { + int average = totalSize / count; + sb.append(" " + smallestSize + ".." + largestSize + + " bytes/item; average " + average + "\n"); + } + + return sb.toString(); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/StringDataItem.java b/dexlib/src/main/java/com/android/dx/dex/file/StringDataItem.java new file mode 100644 index 000000000..38be34492 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/StringDataItem.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.Leb128; +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; + +/** + * Representation of string data for a particular string, in a Dalvik file. + */ +public final class StringDataItem extends OffsettedItem { + /** {@code non-null;} the string value */ + private final CstString value; + + /** + * Constructs an instance. + * + * @param value {@code non-null;} the string value + */ + public StringDataItem(CstString value) { + super(1, writeSize(value)); + + this.value = value; + } + + /** + * Gets the write size for a given value. + * + * @param value {@code non-null;} the string value + * @return {@code >= 2}; the write size, in bytes + */ + private static int writeSize(CstString value) { + int utf16Size = value.getUtf16Size(); + + // The +1 is for the '\0' termination byte. + return Leb128.unsignedLeb128Size(utf16Size) + + value.getUtf8Size() + 1; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_STRING_DATA_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // Nothing to do here. + } + + /** {@inheritDoc} */ + @Override + public void writeTo0(DexFile file, AnnotatedOutput out) { + ByteArray bytes = value.getBytes(); + int utf16Size = value.getUtf16Size(); + + if (out.annotates()) { + out.annotate(Leb128.unsignedLeb128Size(utf16Size), + "utf16_size: " + Hex.u4(utf16Size)); + out.annotate(bytes.size() + 1, value.toQuoted()); + } + + out.writeUleb128(utf16Size); + out.write(bytes); + out.writeByte(0); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return value.toQuoted(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + StringDataItem otherData = (StringDataItem) other; + + return value.compareTo(otherData.value); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/StringIdItem.java b/dexlib/src/main/java/com/android/dx/dex/file/StringIdItem.java new file mode 100644 index 000000000..b3e7d02ab --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/StringIdItem.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.SizeOf; +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +/** + * Representation of a string inside a Dalvik file. + */ +public final class StringIdItem + extends IndexedItem implements Comparable { + /** {@code non-null;} the string value */ + private final CstString value; + + /** {@code null-ok;} associated string data object, if known */ + private StringDataItem data; + + /** + * Constructs an instance. + * + * @param value {@code non-null;} the string value + */ + public StringIdItem(CstString value) { + if (value == null) { + throw new NullPointerException("value == null"); + } + + this.value = value; + this.data = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof StringIdItem)) { + return false; + } + + StringIdItem otherString = (StringIdItem) other; + return value.equals(otherString.value); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** {@inheritDoc} */ + public int compareTo(Object other) { + StringIdItem otherString = (StringIdItem) other; + return value.compareTo(otherString.value); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_STRING_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.STRING_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + if (data == null) { + // The string data hasn't yet been added, so add it. + MixedItemSection stringData = file.getStringData(); + data = new StringDataItem(value); + stringData.add(data); + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + int dataOff = data.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, indexString() + ' ' + value.toQuoted(100)); + out.annotate(4, " string_data_off: " + Hex.u4(dataOff)); + } + + out.writeInt(dataOff); + } + + /** + * Gets the string value. + * + * @return {@code non-null;} the value + */ + public CstString getValue() { + return value; + } + + /** + * Gets the associated data object for this instance, if known. + * + * @return {@code null-ok;} the associated data object or {@code null} + * if not yet known + */ + public StringDataItem getData() { + return data; + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/StringIdsSection.java b/dexlib/src/main/java/com/android/dx/dex/file/StringIdsSection.java new file mode 100644 index 000000000..6826c5a48 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/StringIdsSection.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.Collection; +import java.util.TreeMap; + +/** + * Strings list section of a {@code .dex} file. + */ +public final class StringIdsSection + extends UniformItemSection { + /** + * {@code non-null;} map from string constants to {@link + * StringIdItem} instances + */ + private final TreeMap strings; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public StringIdsSection(DexFile file) { + super("string_ids", file, 4); + + strings = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return strings.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + IndexedItem result = strings.get((CstString) cst); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = strings.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "string_ids_size: " + Hex.u4(sz)); + out.annotate(4, "string_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern, as a regular Java + * {@code String} + * @return {@code non-null;} the interned string + */ + public StringIdItem intern(String string) { + return intern(new StringIdItem(new CstString(string))); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern, as a constant + * @return {@code non-null;} the interned string + */ + public StringIdItem intern(CstString string) { + return intern(new StringIdItem(string)); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern + * @return {@code non-null;} the interned string + */ + public synchronized StringIdItem intern(StringIdItem string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + throwIfPrepared(); + + CstString value = string.getValue(); + StringIdItem already = strings.get(value); + + if (already != null) { + return already; + } + + strings.put(value, string); + return string; + } + + /** + * Interns the components of a name-and-type into this instance. + * + * @param nat {@code non-null;} the name-and-type + */ + public synchronized void intern(CstNat nat) { + intern(nat.getName()); + intern(nat.getDescriptor()); + } + + /** + * Gets the index of the given string, which must have been added + * to this instance. + * + * @param string {@code non-null;} the string to look up + * @return {@code >= 0;} the string's index + */ + public int indexOf(CstString string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + throwIfNotPrepared(); + + StringIdItem s = strings.get(string); + + if (s == null) { + throw new IllegalArgumentException("not found"); + } + + return s.getIndex(); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (StringIdItem s : strings.values()) { + s.setIndex(idx); + idx++; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/TypeIdItem.java b/dexlib/src/main/java/com/android/dx/dex/file/TypeIdItem.java new file mode 100644 index 000000000..e585f1f69 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/TypeIdItem.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.SizeOf; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +/** + * Representation of a type reference inside a Dalvik file. + */ +public final class TypeIdItem extends IdItem { + /** + * Constructs an instance. + * + * @param type {@code non-null;} the constant for the type + */ + public TypeIdItem(CstType type) { + super(type); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_TYPE_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return SizeOf.TYPE_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + file.getStringIds().intern(getDefiningClass().getDescriptor()); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + CstType type = getDefiningClass(); + CstString descriptor = type.getDescriptor(); + int idx = file.getStringIds().indexOf(descriptor); + + if (out.annotates()) { + out.annotate(0, indexString() + ' ' + descriptor.toHuman()); + out.annotate(4, " descriptor_idx: " + Hex.u4(idx)); + } + + out.writeInt(idx); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/TypeIdsSection.java b/dexlib/src/main/java/com/android/dx/dex/file/TypeIdsSection.java new file mode 100644 index 000000000..2245c1a58 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/TypeIdsSection.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.DexFormat; +import com.android.dex.DexIndexOverflowException; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Type identifiers list section of a {@code .dex} file. + */ +public final class TypeIdsSection extends UniformItemSection { + /** + * {@code non-null;} map from types to {@link TypeIdItem} instances + */ + private final TreeMap typeIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public TypeIdsSection(DexFile file) { + super("type_ids", file, 4); + + typeIds = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public Collection items() { + return typeIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + Type type = ((CstType) cst).getClassType(); + IndexedItem result = typeIds.get(type); + + if (result == null) { + throw new IllegalArgumentException("not found: " + cst); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = typeIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (sz > DexFormat.MAX_TYPE_IDX + 1) { + throw new DexIndexOverflowException( + String.format("Too many type identifiers to fit in one dex file: %1$d; max is %2$d.%n" + + "You may try using multi-dex. If multi-dex is enabled then the list of " + + "classes for the main dex list is too large.", + items().size(), DexFormat.MAX_MEMBER_IDX + 1)); + } + + if (out.annotates()) { + out.annotate(4, "type_ids_size: " + Hex.u4(sz)); + out.annotate(4, "type_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param type {@code non-null;} the type to intern + * @return {@code non-null;} the interned reference + */ + public synchronized TypeIdItem intern(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + throwIfPrepared(); + + TypeIdItem result = typeIds.get(type); + + if (result == null) { + result = new TypeIdItem(new CstType(type)); + typeIds.put(type, result); + } + + return result; + } + + /** + * Interns an element into this instance. + * + * @param type {@code non-null;} the type to intern + * @return {@code non-null;} the interned reference + */ + public synchronized TypeIdItem intern(CstType type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + throwIfPrepared(); + + Type typePerSe = type.getClassType(); + TypeIdItem result = typeIds.get(typePerSe); + + if (result == null) { + result = new TypeIdItem(type); + typeIds.put(typePerSe, result); + } + + return result; + } + + /** + * Gets the index of the given type, which must have + * been added to this instance. + * + * @param type {@code non-null;} the type to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + throwIfNotPrepared(); + + TypeIdItem item = typeIds.get(type); + + if (item == null) { + throw new IllegalArgumentException("not found: " + type); + } + + return item.getIndex(); + } + + /** + * Gets the index of the given type, which must have + * been added to this instance. + * + * @param type {@code non-null;} the type to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(CstType type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + return indexOf(type.getClassType()); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (Object i : items()) { + ((TypeIdItem) i).setIndex(idx); + idx++; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/TypeListItem.java b/dexlib/src/main/java/com/android/dx/dex/file/TypeListItem.java new file mode 100644 index 000000000..1f9b6a8ef --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/TypeListItem.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; + +/** + * Representation of a list of class references. + */ +public final class TypeListItem extends OffsettedItem { + /** alignment requirement */ + private static final int ALIGNMENT = 4; + + /** element size in bytes */ + private static final int ELEMENT_SIZE = 2; + + /** header size in bytes */ + private static final int HEADER_SIZE = 4; + + /** {@code non-null;} the actual list */ + private final TypeList list; + + /** + * Constructs an instance. + * + * @param list {@code non-null;} the actual list + */ + public TypeListItem(TypeList list) { + super(ALIGNMENT, (list.size() * ELEMENT_SIZE) + HEADER_SIZE); + + this.list = list; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return StdTypeList.hashContents(list); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_TYPE_LIST; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + int sz = list.size(); + + for (int i = 0; i < sz; i++) { + typeIds.intern(list.getType(i)); + } + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** + * Gets the underlying list. + * + * @return {@code non-null;} the list + */ + public TypeList getList() { + return list; + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + TypeIdsSection typeIds = file.getTypeIds(); + int sz = list.size(); + + if (out.annotates()) { + out.annotate(0, offsetString() + " type_list"); + out.annotate(HEADER_SIZE, " size: " + Hex.u4(sz)); + for (int i = 0; i < sz; i++) { + Type one = list.getType(i); + int idx = typeIds.indexOf(one); + out.annotate(ELEMENT_SIZE, + " " + Hex.u2(idx) + " // " + one.toHuman()); + } + } + + out.writeInt(sz); + + for (int i = 0; i < sz; i++) { + out.writeShort(typeIds.indexOf(list.getType(i))); + } + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + TypeList thisList = this.list; + TypeList otherList = ((TypeListItem) other).list; + + return StdTypeList.compareContents(thisList, otherList); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/UniformItemSection.java b/dexlib/src/main/java/com/android/dx/dex/file/UniformItemSection.java new file mode 100644 index 000000000..ade268fa8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/UniformItemSection.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.util.AnnotatedOutput; +import java.util.Collection; + +/** + * A section of a {@code .dex} file which consists of a sequence of + * {@link Item} objects. Each of the items must have the same size in + * the output. + */ +public abstract class UniformItemSection extends Section { + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + * @param alignment {@code > 0;} alignment requirement for the final output; + * must be a power of 2 + */ + public UniformItemSection(String name, DexFile file, int alignment) { + super(name, file, alignment); + } + + /** {@inheritDoc} */ + @Override + public final int writeSize() { + Collection items = items(); + int sz = items.size(); + + if (sz == 0) { + return 0; + } + + // Since each item has to be the same size, we can pick any. + return sz * items.iterator().next().writeSize(); + } + + /** + * Gets the item corresponding to the given {@link Constant}. This + * will throw an exception if the constant is not found, including + * if this instance isn't the sort that maps constants to {@link + * IndexedItem} instances. + * + * @param cst {@code non-null;} constant to look for + * @return {@code non-null;} the corresponding item found in this instance + */ + public abstract IndexedItem get(Constant cst); + + /** {@inheritDoc} */ + @Override + protected final void prepare0() { + DexFile file = getFile(); + + orderItems(); + + for (Item one : items()) { + one.addContents(file); + } + } + + /** {@inheritDoc} */ + @Override + protected final void writeTo0(AnnotatedOutput out) { + DexFile file = getFile(); + int alignment = getAlignment(); + + for (Item one : items()) { + one.writeTo(file, out); + out.alignTo(alignment); + } + } + + /** {@inheritDoc} */ + @Override + public final int getAbsoluteItemOffset(Item item) { + /* + * Since all items must be the same size, we can use the size + * of the one we're given to calculate its offset. + */ + IndexedItem ii = (IndexedItem) item; + int relativeOffset = ii.getIndex() * ii.writeSize(); + + return getAbsoluteOffset(relativeOffset); + } + + /** + * Alters or picks the order for items in this instance if desired, + * so that subsequent calls to {@link #items} will yield a + * so-ordered collection. If the items in this instance are indexed, + * then this method should also assign indices. + */ + protected abstract void orderItems(); +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/UniformListItem.java b/dexlib/src/main/java/com/android/dx/dex/file/UniformListItem.java new file mode 100644 index 000000000..0196ca19c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/UniformListItem.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.List; + +/** + * Class that represents a contiguous list of uniform items. Each + * item in the list, in particular, must have the same write size and + * alignment. + * + *

This class inherits its alignment from its items, bumped up to + * {@code 4} if the items have a looser alignment requirement. If + * it is more than {@code 4}, then there will be a gap after the + * output list size (which is four bytes) and before the first item.

+ * + * @param type of element contained in an instance + */ +public final class UniformListItem + extends OffsettedItem { + /** the size of the list header */ + private static final int HEADER_SIZE = 4; + + /** {@code non-null;} the item type */ + private final ItemType itemType; + + /** {@code non-null;} the contents */ + private final List items; + + /** + * Constructs an instance. It is illegal to modify the given list once + * it is used to construct an instance of this class. + * + * @param itemType {@code non-null;} the type of the item + * @param items {@code non-null and non-empty;} list of items to represent + */ + public UniformListItem(ItemType itemType, List items) { + super(getAlignment(items), writeSize(items)); + + if (itemType == null) { + throw new NullPointerException("itemType == null"); + } + + this.items = items; + this.itemType = itemType; + } + + /** + * Helper for {@link #UniformListItem}, which returns the alignment + * requirement implied by the given list. See the header comment for + * more details. + * + * @param items {@code non-null;} list of items being represented + * @return {@code >= 4;} the alignment requirement + */ + private static int getAlignment(List items) { + try { + // Since they all must have the same alignment, any one will do. + return Math.max(HEADER_SIZE, items.get(0).getAlignment()); + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("items.size() == 0"); + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("items == null"); + } + } + + /** + * Calculates the write size for the given list. + * + * @param items {@code non-null;} the list in question + * @return {@code >= 0;} the write size + */ + private static int writeSize(List items) { + /* + * This class assumes all included items are the same size, + * an assumption which is verified in place0(). + */ + OffsettedItem first = items.get(0); + return (items.size() * first.writeSize()) + getAlignment(items); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return itemType; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append(items); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + for (OffsettedItem i : items) { + i.addContents(file); + } + } + + /** {@inheritDoc} */ + @Override + public final String toHuman() { + StringBuffer sb = new StringBuffer(100); + boolean first = true; + + sb.append("{"); + + for (OffsettedItem i : items) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(i.toHuman()); + } + + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the underlying list of items. + * + * @return {@code non-null;} the list + */ + public final List getItems() { + return items; + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + offset += headerSize(); + + boolean first = true; + int theSize = -1; + int theAlignment = -1; + + for (OffsettedItem i : items) { + int size = i.writeSize(); + if (first) { + theSize = size; + theAlignment = i.getAlignment(); + first = false; + } else { + if (size != theSize) { + throw new UnsupportedOperationException( + "item size mismatch"); + } + if (i.getAlignment() != theAlignment) { + throw new UnsupportedOperationException( + "item alignment mismatch"); + } + } + + offset = i.place(addedTo, offset) + size; + } + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + int size = items.size(); + + if (out.annotates()) { + out.annotate(0, offsetString() + " " + typeName()); + out.annotate(4, " size: " + Hex.u4(size)); + } + + out.writeInt(size); + + for (OffsettedItem i : items) { + i.writeTo(file, out); + } + } + + /** + * Get the size of the header of this list. + * + * @return {@code >= 0;} the header size + */ + private int headerSize() { + /* + * Because of how this instance was set up, this is the same + * as the alignment. + */ + return getAlignment(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/dex/file/ValueEncoder.java b/dexlib/src/main/java/com/android/dx/dex/file/ValueEncoder.java new file mode 100644 index 000000000..fa807a7e4 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/dex/file/ValueEncoder.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.dex.file; + +import com.android.dex.EncodedValueCodec; +import com.android.dx.rop.annotation.Annotation; +import com.android.dx.rop.annotation.NameValuePair; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstAnnotation; +import com.android.dx.rop.cst.CstArray; +import com.android.dx.rop.cst.CstBoolean; +import com.android.dx.rop.cst.CstByte; +import com.android.dx.rop.cst.CstChar; +import com.android.dx.rop.cst.CstDouble; +import com.android.dx.rop.cst.CstEnumRef; +import com.android.dx.rop.cst.CstFieldRef; +import com.android.dx.rop.cst.CstFloat; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.cst.CstKnownNull; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.rop.cst.CstLong; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstShort; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.AnnotatedOutput; +import com.android.dx.util.Hex; +import java.util.Collection; + +/** + * Handler for writing out {@code encoded_values} and parts + * thereof. + */ +public final class ValueEncoder { + /** annotation value type constant: {@code byte} */ + private static final int VALUE_BYTE = 0x00; + + /** annotation value type constant: {@code short} */ + private static final int VALUE_SHORT = 0x02; + + /** annotation value type constant: {@code char} */ + private static final int VALUE_CHAR = 0x03; + + /** annotation value type constant: {@code int} */ + private static final int VALUE_INT = 0x04; + + /** annotation value type constant: {@code long} */ + private static final int VALUE_LONG = 0x06; + + /** annotation value type constant: {@code float} */ + private static final int VALUE_FLOAT = 0x10; + + /** annotation value type constant: {@code double} */ + private static final int VALUE_DOUBLE = 0x11; + + /** annotation value type constant: {@code string} */ + private static final int VALUE_STRING = 0x17; + + /** annotation value type constant: {@code type} */ + private static final int VALUE_TYPE = 0x18; + + /** annotation value type constant: {@code field} */ + private static final int VALUE_FIELD = 0x19; + + /** annotation value type constant: {@code method} */ + private static final int VALUE_METHOD = 0x1a; + + /** annotation value type constant: {@code enum} */ + private static final int VALUE_ENUM = 0x1b; + + /** annotation value type constant: {@code array} */ + private static final int VALUE_ARRAY = 0x1c; + + /** annotation value type constant: {@code annotation} */ + private static final int VALUE_ANNOTATION = 0x1d; + + /** annotation value type constant: {@code null} */ + private static final int VALUE_NULL = 0x1e; + + /** annotation value type constant: {@code boolean} */ + private static final int VALUE_BOOLEAN = 0x1f; + + /** {@code non-null;} file being written */ + private final DexFile file; + + /** {@code non-null;} output stream to write to */ + private final AnnotatedOutput out; + + /** + * Construct an instance. + * + * @param file {@code non-null;} file being written + * @param out {@code non-null;} output stream to write to + */ + public ValueEncoder(DexFile file, AnnotatedOutput out) { + if (file == null) { + throw new NullPointerException("file == null"); + } + + if (out == null) { + throw new NullPointerException("out == null"); + } + + this.file = file; + this.out = out; + } + + /** + * Writes out the encoded form of the given constant. + * + * @param cst {@code non-null;} the constant to write + */ + public void writeConstant(Constant cst) { + int type = constantToValueType(cst); + int arg; + + switch (type) { + case VALUE_BYTE: + case VALUE_SHORT: + case VALUE_INT: + case VALUE_LONG: { + long value = ((CstLiteralBits) cst).getLongBits(); + EncodedValueCodec.writeSignedIntegralValue(out, type, value); + break; + } + case VALUE_CHAR: { + long value = ((CstLiteralBits) cst).getLongBits(); + EncodedValueCodec.writeUnsignedIntegralValue(out, type, value); + break; + } + case VALUE_FLOAT: { + // Shift value left 32 so that right-zero-extension works. + long value = ((CstFloat) cst).getLongBits() << 32; + EncodedValueCodec.writeRightZeroExtendedValue(out, type, value); + break; + } + case VALUE_DOUBLE: { + long value = ((CstDouble) cst).getLongBits(); + EncodedValueCodec.writeRightZeroExtendedValue(out, type, value); + break; + } + case VALUE_STRING: { + int index = file.getStringIds().indexOf((CstString) cst); + EncodedValueCodec.writeUnsignedIntegralValue(out, type, (long) index); + break; + } + case VALUE_TYPE: { + int index = file.getTypeIds().indexOf((CstType) cst); + EncodedValueCodec.writeUnsignedIntegralValue(out, type, (long) index); + break; + } + case VALUE_FIELD: { + int index = file.getFieldIds().indexOf((CstFieldRef) cst); + EncodedValueCodec.writeUnsignedIntegralValue(out, type, (long) index); + break; + } + case VALUE_METHOD: { + int index = file.getMethodIds().indexOf((CstMethodRef) cst); + EncodedValueCodec.writeUnsignedIntegralValue(out, type, (long) index); + break; + } + case VALUE_ENUM: { + CstFieldRef fieldRef = ((CstEnumRef) cst).getFieldRef(); + int index = file.getFieldIds().indexOf(fieldRef); + EncodedValueCodec.writeUnsignedIntegralValue(out, type, (long) index); + break; + } + case VALUE_ARRAY: { + out.writeByte(type); + writeArray((CstArray) cst, false); + break; + } + case VALUE_ANNOTATION: { + out.writeByte(type); + writeAnnotation(((CstAnnotation) cst).getAnnotation(), + false); + break; + } + case VALUE_NULL: { + out.writeByte(type); + break; + } + case VALUE_BOOLEAN: { + int value = ((CstBoolean) cst).getIntBits(); + out.writeByte(type | (value << 5)); + break; + } + default: { + throw new RuntimeException("Shouldn't happen"); + } + } + } + + /** + * Gets the value type for the given constant. + * + * @param cst {@code non-null;} the constant + * @return the value type; one of the {@code VALUE_*} constants + * defined by this class + */ + private static int constantToValueType(Constant cst) { + /* + * TODO: Constant should probable have an associated enum, so this + * can be a switch(). + */ + if (cst instanceof CstByte) { + return VALUE_BYTE; + } else if (cst instanceof CstShort) { + return VALUE_SHORT; + } else if (cst instanceof CstChar) { + return VALUE_CHAR; + } else if (cst instanceof CstInteger) { + return VALUE_INT; + } else if (cst instanceof CstLong) { + return VALUE_LONG; + } else if (cst instanceof CstFloat) { + return VALUE_FLOAT; + } else if (cst instanceof CstDouble) { + return VALUE_DOUBLE; + } else if (cst instanceof CstString) { + return VALUE_STRING; + } else if (cst instanceof CstType) { + return VALUE_TYPE; + } else if (cst instanceof CstFieldRef) { + return VALUE_FIELD; + } else if (cst instanceof CstMethodRef) { + return VALUE_METHOD; + } else if (cst instanceof CstEnumRef) { + return VALUE_ENUM; + } else if (cst instanceof CstArray) { + return VALUE_ARRAY; + } else if (cst instanceof CstAnnotation) { + return VALUE_ANNOTATION; + } else if (cst instanceof CstKnownNull) { + return VALUE_NULL; + } else if (cst instanceof CstBoolean) { + return VALUE_BOOLEAN; + } else { + throw new RuntimeException("Shouldn't happen"); + } + } + + /** + * Writes out the encoded form of the given array, that is, as + * an {@code encoded_array} and not including a + * {@code value_type} prefix. If the output stream keeps + * (debugging) annotations and {@code topLevel} is + * {@code true}, then this method will write (debugging) + * annotations. + * + * @param array {@code non-null;} array instance to write + * @param topLevel {@code true} iff the given annotation is the + * top-level annotation or {@code false} if it is a sub-annotation + * of some other annotation + */ + public void writeArray(CstArray array, boolean topLevel) { + boolean annotates = topLevel && out.annotates(); + CstArray.List list = ((CstArray) array).getList(); + int size = list.size(); + + if (annotates) { + out.annotate(" size: " + Hex.u4(size)); + } + + out.writeUleb128(size); + + for (int i = 0; i < size; i++) { + Constant cst = list.get(i); + if (annotates) { + out.annotate(" [" + Integer.toHexString(i) + "] " + + constantToHuman(cst)); + } + writeConstant(cst); + } + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Writes out the encoded form of the given annotation, that is, + * as an {@code encoded_annotation} and not including a + * {@code value_type} prefix. If the output stream keeps + * (debugging) annotations and {@code topLevel} is + * {@code true}, then this method will write (debugging) + * annotations. + * + * @param annotation {@code non-null;} annotation instance to write + * @param topLevel {@code true} iff the given annotation is the + * top-level annotation or {@code false} if it is a sub-annotation + * of some other annotation + */ + public void writeAnnotation(Annotation annotation, boolean topLevel) { + boolean annotates = topLevel && out.annotates(); + StringIdsSection stringIds = file.getStringIds(); + TypeIdsSection typeIds = file.getTypeIds(); + + CstType type = annotation.getType(); + int typeIdx = typeIds.indexOf(type); + + if (annotates) { + out.annotate(" type_idx: " + Hex.u4(typeIdx) + " // " + + type.toHuman()); + } + + out.writeUleb128(typeIds.indexOf(annotation.getType())); + + Collection pairs = annotation.getNameValuePairs(); + int size = pairs.size(); + + if (annotates) { + out.annotate(" size: " + Hex.u4(size)); + } + + out.writeUleb128(size); + + int at = 0; + for (NameValuePair pair : pairs) { + CstString name = pair.getName(); + int nameIdx = stringIds.indexOf(name); + Constant value = pair.getValue(); + + if (annotates) { + out.annotate(0, " elements[" + at + "]:"); + at++; + out.annotate(" name_idx: " + Hex.u4(nameIdx) + " // " + + name.toHuman()); + } + + out.writeUleb128(nameIdx); + + if (annotates) { + out.annotate(" value: " + constantToHuman(value)); + } + + writeConstant(value); + } + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Gets the colloquial type name and human form of the type of the + * given constant, when used as an encoded value. + * + * @param cst {@code non-null;} the constant + * @return {@code non-null;} its type name and human form + */ + public static String constantToHuman(Constant cst) { + int type = constantToValueType(cst); + + if (type == VALUE_NULL) { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + + sb.append(cst.typeName()); + sb.append(' '); + sb.append(cst.toHuman()); + + return sb.toString(); + } + + /** + * Helper for {@code addContents()} methods, which adds + * contents for a particular {@link Annotation}, calling itself + * recursively should it encounter a nested annotation. + * + * @param file {@code non-null;} the file to add to + * @param annotation {@code non-null;} the annotation to add contents for + */ + public static void addContents(DexFile file, Annotation annotation) { + TypeIdsSection typeIds = file.getTypeIds(); + StringIdsSection stringIds = file.getStringIds(); + + typeIds.intern(annotation.getType()); + + for (NameValuePair pair : annotation.getNameValuePairs()) { + stringIds.intern(pair.getName()); + addContents(file, pair.getValue()); + } + } + + /** + * Helper for {@code addContents()} methods, which adds + * contents for a particular constant, calling itself recursively + * should it encounter a {@link CstArray} and calling {@link + * #addContents(DexFile,Annotation)} recursively should it + * encounter a {@link CstAnnotation}. + * + * @param file {@code non-null;} the file to add to + * @param cst {@code non-null;} the constant to add contents for + */ + public static void addContents(DexFile file, Constant cst) { + if (cst instanceof CstAnnotation) { + addContents(file, ((CstAnnotation) cst).getAnnotation()); + } else if (cst instanceof CstArray) { + CstArray.List list = ((CstArray) cst).getList(); + int size = list.size(); + for (int i = 0; i < size; i++) { + addContents(file, list.get(i)); + } + } else { + file.internIfAppropriate(cst); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/CodeReader.java b/dexlib/src/main/java/com/android/dx/io/CodeReader.java new file mode 100644 index 000000000..5fd5cf696 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/CodeReader.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io; + +import com.android.dex.DexException; +import com.android.dx.io.instructions.DecodedInstruction; + +/** + * Walks through a block of code and calls visitor call backs. + */ +public final class CodeReader { + private Visitor fallbackVisitor = null; + private Visitor stringVisitor = null; + private Visitor typeVisitor = null; + private Visitor fieldVisitor = null; + private Visitor methodVisitor = null; + + /** + * Sets {@code visitor} as the visitor for all instructions. + */ + public void setAllVisitors(Visitor visitor) { + fallbackVisitor = visitor; + stringVisitor = visitor; + typeVisitor = visitor; + fieldVisitor = visitor; + methodVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all instructions not + * otherwise handled. + */ + public void setFallbackVisitor(Visitor visitor) { + fallbackVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all string instructions. + */ + public void setStringVisitor(Visitor visitor) { + stringVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all type instructions. + */ + public void setTypeVisitor(Visitor visitor) { + typeVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all field instructions. + */ + public void setFieldVisitor(Visitor visitor) { + fieldVisitor = visitor; + } + + /** + * Sets {@code visitor} as the visitor for all method instructions. + */ + public void setMethodVisitor(Visitor visitor) { + methodVisitor = visitor; + } + + public void visitAll(DecodedInstruction[] decodedInstructions) + throws DexException { + int size = decodedInstructions.length; + + for (int i = 0; i < size; i++) { + DecodedInstruction one = decodedInstructions[i]; + if (one == null) { + continue; + } + + callVisit(decodedInstructions, one); + } + } + + public void visitAll(short[] encodedInstructions) throws DexException { + DecodedInstruction[] decodedInstructions = + DecodedInstruction.decodeAll(encodedInstructions); + visitAll(decodedInstructions); + } + + private void callVisit(DecodedInstruction[] all, DecodedInstruction one) { + Visitor visitor = null; + + switch (OpcodeInfo.getIndexType(one.getOpcode())) { + case STRING_REF: visitor = stringVisitor; break; + case TYPE_REF: visitor = typeVisitor; break; + case FIELD_REF: visitor = fieldVisitor; break; + case METHOD_REF: visitor = methodVisitor; break; + } + + if (visitor == null) { + visitor = fallbackVisitor; + } + + if (visitor != null) { + visitor.visit(all, one); + } + } + + public interface Visitor { + void visit(DecodedInstruction[] all, DecodedInstruction one); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/DexIndexPrinter.java b/dexlib/src/main/java/com/android/dx/io/DexIndexPrinter.java new file mode 100644 index 000000000..2b89d44e7 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/DexIndexPrinter.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io; + +import com.android.dex.ClassDef; +import com.android.dex.Dex; +import com.android.dex.FieldId; +import com.android.dex.MethodId; +import com.android.dex.ProtoId; +import com.android.dex.TableOfContents; +import java.io.File; +import java.io.IOException; + +/** + * Executable that prints all indices of a dex file. + */ +public final class DexIndexPrinter { + private final Dex dex; + private final TableOfContents tableOfContents; + + public DexIndexPrinter(File file) throws IOException { + this.dex = new Dex(file); + this.tableOfContents = dex.getTableOfContents(); + } + + private void printMap() { + for (TableOfContents.Section section : tableOfContents.sections) { + if (section.off != -1) { + System.out.println("section " + Integer.toHexString(section.type) + + " off=" + Integer.toHexString(section.off) + + " size=" + Integer.toHexString(section.size) + + " byteCount=" + Integer.toHexString(section.byteCount)); + } + } + } + + private void printStrings() throws IOException { + int index = 0; + for (String string : dex.strings()) { + System.out.println("string " + index + ": " + string); + index++; + } + } + + private void printTypeIds() throws IOException { + int index = 0; + for (Integer type : dex.typeIds()) { + System.out.println("type " + index + ": " + dex.strings().get(type)); + index++; + } + } + + private void printProtoIds() throws IOException { + int index = 0; + for (ProtoId protoId : dex.protoIds()) { + System.out.println("proto " + index + ": " + protoId); + index++; + } + } + + private void printFieldIds() throws IOException { + int index = 0; + for (FieldId fieldId : dex.fieldIds()) { + System.out.println("field " + index + ": " + fieldId); + index++; + } + } + + private void printMethodIds() throws IOException { + int index = 0; + for (MethodId methodId : dex.methodIds()) { + System.out.println("methodId " + index + ": " + methodId); + index++; + } + } + + private void printTypeLists() throws IOException { + if (tableOfContents.typeLists.off == -1) { + System.out.println("No type lists"); + return; + } + Dex.Section in = dex.open(tableOfContents.typeLists.off); + for (int i = 0; i < tableOfContents.typeLists.size; i++) { + int size = in.readInt(); + System.out.print("Type list i=" + i + ", size=" + size + ", elements="); + for (int t = 0; t < size; t++) { + System.out.print(" " + dex.typeNames().get((int) in.readShort())); + } + if (size % 2 == 1) { + in.readShort(); // retain alignment + } + System.out.println(); + } + } + + private void printClassDefs() { + int index = 0; + for (ClassDef classDef : dex.classDefs()) { + System.out.println("class def " + index + ": " + classDef); + index++; + } + } + + public static void main(String[] args) throws IOException { + DexIndexPrinter indexPrinter = new DexIndexPrinter(new File(args[0])); + indexPrinter.printMap(); + indexPrinter.printStrings(); + indexPrinter.printTypeIds(); + indexPrinter.printProtoIds(); + indexPrinter.printFieldIds(); + indexPrinter.printMethodIds(); + indexPrinter.printTypeLists(); + indexPrinter.printClassDefs(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/IndexType.java b/dexlib/src/main/java/com/android/dx/io/IndexType.java new file mode 100644 index 000000000..0bd142a89 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/IndexType.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io; + +/** + * The various types that an index in a Dalvik instruction might refer to. + */ +public enum IndexType { + /** "Unknown." Used for undefined opcodes. */ + UNKNOWN, + + /** no index used */ + NONE, + + /** "It depends." Used for {@code throw-verification-error}. */ + VARIES, + + /** type reference index */ + TYPE_REF, + + /** string reference index */ + STRING_REF, + + /** method reference index */ + METHOD_REF, + + /** field reference index */ + FIELD_REF, + + /** method index and a proto index */ + METHOD_AND_PROTO_REF, + + /** call site reference index */ + CALL_SITE_REF, + + /** inline method index (for inline linked method invocations) */ + INLINE_METHOD, + + /** direct vtable offset (for static linked method invocations) */ + VTABLE_OFFSET, + + /** direct field offset (for static linked field accesses) */ + FIELD_OFFSET; +} diff --git a/dexlib/src/main/java/com/android/dx/io/OpcodeInfo.java b/dexlib/src/main/java/com/android/dx/io/OpcodeInfo.java new file mode 100644 index 000000000..500abf7a2 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/OpcodeInfo.java @@ -0,0 +1,1285 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io; + +import com.android.dx.io.instructions.InstructionCodec; +import com.android.dx.util.Hex; + +/** + * Information about each Dalvik opcode. + */ +public final class OpcodeInfo { + /* + * TODO: Merge at least most of the info from the Dops class into + * this one. + */ + + /** non-null; array containing all the information */ + private static final Info[] INFO; + + /** + * pseudo-opcode used for nonstandard formatted "instructions" + * (which are mostly not actually instructions, though they do + * appear in instruction lists). TODO: Retire the usage of this + * constant. + */ + public static final Info SPECIAL_FORMAT = + new Info(Opcodes.SPECIAL_FORMAT, "", + InstructionCodec.FORMAT_00X, IndexType.NONE); + + // TODO: These payload opcodes should be generated by opcode-gen. + + public static final Info PACKED_SWITCH_PAYLOAD = + new Info(Opcodes.PACKED_SWITCH_PAYLOAD, "packed-switch-payload", + InstructionCodec.FORMAT_PACKED_SWITCH_PAYLOAD, + IndexType.NONE); + + public static final Info SPARSE_SWITCH_PAYLOAD = + new Info(Opcodes.SPARSE_SWITCH_PAYLOAD, "sparse-switch-payload", + InstructionCodec.FORMAT_SPARSE_SWITCH_PAYLOAD, + IndexType.NONE); + + public static final Info FILL_ARRAY_DATA_PAYLOAD = + new Info(Opcodes.FILL_ARRAY_DATA_PAYLOAD, "fill-array-data-payload", + InstructionCodec.FORMAT_FILL_ARRAY_DATA_PAYLOAD, + IndexType.NONE); + + // BEGIN(opcode-info-defs); GENERATED AUTOMATICALLY BY opcode-gen + public static final Info NOP = + new Info(Opcodes.NOP, "nop", + InstructionCodec.FORMAT_10X, IndexType.NONE); + + public static final Info MOVE = + new Info(Opcodes.MOVE, "move", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MOVE_FROM16 = + new Info(Opcodes.MOVE_FROM16, "move/from16", + InstructionCodec.FORMAT_22X, IndexType.NONE); + + public static final Info MOVE_16 = + new Info(Opcodes.MOVE_16, "move/16", + InstructionCodec.FORMAT_32X, IndexType.NONE); + + public static final Info MOVE_WIDE = + new Info(Opcodes.MOVE_WIDE, "move-wide", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MOVE_WIDE_FROM16 = + new Info(Opcodes.MOVE_WIDE_FROM16, "move-wide/from16", + InstructionCodec.FORMAT_22X, IndexType.NONE); + + public static final Info MOVE_WIDE_16 = + new Info(Opcodes.MOVE_WIDE_16, "move-wide/16", + InstructionCodec.FORMAT_32X, IndexType.NONE); + + public static final Info MOVE_OBJECT = + new Info(Opcodes.MOVE_OBJECT, "move-object", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MOVE_OBJECT_FROM16 = + new Info(Opcodes.MOVE_OBJECT_FROM16, "move-object/from16", + InstructionCodec.FORMAT_22X, IndexType.NONE); + + public static final Info MOVE_OBJECT_16 = + new Info(Opcodes.MOVE_OBJECT_16, "move-object/16", + InstructionCodec.FORMAT_32X, IndexType.NONE); + + public static final Info MOVE_RESULT = + new Info(Opcodes.MOVE_RESULT, "move-result", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info MOVE_RESULT_WIDE = + new Info(Opcodes.MOVE_RESULT_WIDE, "move-result-wide", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info MOVE_RESULT_OBJECT = + new Info(Opcodes.MOVE_RESULT_OBJECT, "move-result-object", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info MOVE_EXCEPTION = + new Info(Opcodes.MOVE_EXCEPTION, "move-exception", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info RETURN_VOID = + new Info(Opcodes.RETURN_VOID, "return-void", + InstructionCodec.FORMAT_10X, IndexType.NONE); + + public static final Info RETURN = + new Info(Opcodes.RETURN, "return", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info RETURN_WIDE = + new Info(Opcodes.RETURN_WIDE, "return-wide", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info RETURN_OBJECT = + new Info(Opcodes.RETURN_OBJECT, "return-object", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info CONST_4 = + new Info(Opcodes.CONST_4, "const/4", + InstructionCodec.FORMAT_11N, IndexType.NONE); + + public static final Info CONST_16 = + new Info(Opcodes.CONST_16, "const/16", + InstructionCodec.FORMAT_21S, IndexType.NONE); + + public static final Info CONST = + new Info(Opcodes.CONST, "const", + InstructionCodec.FORMAT_31I, IndexType.NONE); + + public static final Info CONST_HIGH16 = + new Info(Opcodes.CONST_HIGH16, "const/high16", + InstructionCodec.FORMAT_21H, IndexType.NONE); + + public static final Info CONST_WIDE_16 = + new Info(Opcodes.CONST_WIDE_16, "const-wide/16", + InstructionCodec.FORMAT_21S, IndexType.NONE); + + public static final Info CONST_WIDE_32 = + new Info(Opcodes.CONST_WIDE_32, "const-wide/32", + InstructionCodec.FORMAT_31I, IndexType.NONE); + + public static final Info CONST_WIDE = + new Info(Opcodes.CONST_WIDE, "const-wide", + InstructionCodec.FORMAT_51L, IndexType.NONE); + + public static final Info CONST_WIDE_HIGH16 = + new Info(Opcodes.CONST_WIDE_HIGH16, "const-wide/high16", + InstructionCodec.FORMAT_21H, IndexType.NONE); + + public static final Info CONST_STRING = + new Info(Opcodes.CONST_STRING, "const-string", + InstructionCodec.FORMAT_21C, IndexType.STRING_REF); + + public static final Info CONST_STRING_JUMBO = + new Info(Opcodes.CONST_STRING_JUMBO, "const-string/jumbo", + InstructionCodec.FORMAT_31C, IndexType.STRING_REF); + + public static final Info CONST_CLASS = + new Info(Opcodes.CONST_CLASS, "const-class", + InstructionCodec.FORMAT_21C, IndexType.TYPE_REF); + + public static final Info MONITOR_ENTER = + new Info(Opcodes.MONITOR_ENTER, "monitor-enter", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info MONITOR_EXIT = + new Info(Opcodes.MONITOR_EXIT, "monitor-exit", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info CHECK_CAST = + new Info(Opcodes.CHECK_CAST, "check-cast", + InstructionCodec.FORMAT_21C, IndexType.TYPE_REF); + + public static final Info INSTANCE_OF = + new Info(Opcodes.INSTANCE_OF, "instance-of", + InstructionCodec.FORMAT_22C, IndexType.TYPE_REF); + + public static final Info ARRAY_LENGTH = + new Info(Opcodes.ARRAY_LENGTH, "array-length", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NEW_INSTANCE = + new Info(Opcodes.NEW_INSTANCE, "new-instance", + InstructionCodec.FORMAT_21C, IndexType.TYPE_REF); + + public static final Info NEW_ARRAY = + new Info(Opcodes.NEW_ARRAY, "new-array", + InstructionCodec.FORMAT_22C, IndexType.TYPE_REF); + + public static final Info FILLED_NEW_ARRAY = + new Info(Opcodes.FILLED_NEW_ARRAY, "filled-new-array", + InstructionCodec.FORMAT_35C, IndexType.TYPE_REF); + + public static final Info FILLED_NEW_ARRAY_RANGE = + new Info(Opcodes.FILLED_NEW_ARRAY_RANGE, "filled-new-array/range", + InstructionCodec.FORMAT_3RC, IndexType.TYPE_REF); + + public static final Info FILL_ARRAY_DATA = + new Info(Opcodes.FILL_ARRAY_DATA, "fill-array-data", + InstructionCodec.FORMAT_31T, IndexType.NONE); + + public static final Info THROW = + new Info(Opcodes.THROW, "throw", + InstructionCodec.FORMAT_11X, IndexType.NONE); + + public static final Info GOTO = + new Info(Opcodes.GOTO, "goto", + InstructionCodec.FORMAT_10T, IndexType.NONE); + + public static final Info GOTO_16 = + new Info(Opcodes.GOTO_16, "goto/16", + InstructionCodec.FORMAT_20T, IndexType.NONE); + + public static final Info GOTO_32 = + new Info(Opcodes.GOTO_32, "goto/32", + InstructionCodec.FORMAT_30T, IndexType.NONE); + + public static final Info PACKED_SWITCH = + new Info(Opcodes.PACKED_SWITCH, "packed-switch", + InstructionCodec.FORMAT_31T, IndexType.NONE); + + public static final Info SPARSE_SWITCH = + new Info(Opcodes.SPARSE_SWITCH, "sparse-switch", + InstructionCodec.FORMAT_31T, IndexType.NONE); + + public static final Info CMPL_FLOAT = + new Info(Opcodes.CMPL_FLOAT, "cmpl-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info CMPG_FLOAT = + new Info(Opcodes.CMPG_FLOAT, "cmpg-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info CMPL_DOUBLE = + new Info(Opcodes.CMPL_DOUBLE, "cmpl-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info CMPG_DOUBLE = + new Info(Opcodes.CMPG_DOUBLE, "cmpg-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info CMP_LONG = + new Info(Opcodes.CMP_LONG, "cmp-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info IF_EQ = + new Info(Opcodes.IF_EQ, "if-eq", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_NE = + new Info(Opcodes.IF_NE, "if-ne", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_LT = + new Info(Opcodes.IF_LT, "if-lt", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_GE = + new Info(Opcodes.IF_GE, "if-ge", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_GT = + new Info(Opcodes.IF_GT, "if-gt", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_LE = + new Info(Opcodes.IF_LE, "if-le", + InstructionCodec.FORMAT_22T, IndexType.NONE); + + public static final Info IF_EQZ = + new Info(Opcodes.IF_EQZ, "if-eqz", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_NEZ = + new Info(Opcodes.IF_NEZ, "if-nez", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_LTZ = + new Info(Opcodes.IF_LTZ, "if-ltz", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_GEZ = + new Info(Opcodes.IF_GEZ, "if-gez", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_GTZ = + new Info(Opcodes.IF_GTZ, "if-gtz", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info IF_LEZ = + new Info(Opcodes.IF_LEZ, "if-lez", + InstructionCodec.FORMAT_21T, IndexType.NONE); + + public static final Info AGET = + new Info(Opcodes.AGET, "aget", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_WIDE = + new Info(Opcodes.AGET_WIDE, "aget-wide", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_OBJECT = + new Info(Opcodes.AGET_OBJECT, "aget-object", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_BOOLEAN = + new Info(Opcodes.AGET_BOOLEAN, "aget-boolean", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_BYTE = + new Info(Opcodes.AGET_BYTE, "aget-byte", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_CHAR = + new Info(Opcodes.AGET_CHAR, "aget-char", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AGET_SHORT = + new Info(Opcodes.AGET_SHORT, "aget-short", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT = + new Info(Opcodes.APUT, "aput", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_WIDE = + new Info(Opcodes.APUT_WIDE, "aput-wide", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_OBJECT = + new Info(Opcodes.APUT_OBJECT, "aput-object", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_BOOLEAN = + new Info(Opcodes.APUT_BOOLEAN, "aput-boolean", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_BYTE = + new Info(Opcodes.APUT_BYTE, "aput-byte", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_CHAR = + new Info(Opcodes.APUT_CHAR, "aput-char", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info APUT_SHORT = + new Info(Opcodes.APUT_SHORT, "aput-short", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info IGET = + new Info(Opcodes.IGET, "iget", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_WIDE = + new Info(Opcodes.IGET_WIDE, "iget-wide", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_OBJECT = + new Info(Opcodes.IGET_OBJECT, "iget-object", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_BOOLEAN = + new Info(Opcodes.IGET_BOOLEAN, "iget-boolean", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_BYTE = + new Info(Opcodes.IGET_BYTE, "iget-byte", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_CHAR = + new Info(Opcodes.IGET_CHAR, "iget-char", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IGET_SHORT = + new Info(Opcodes.IGET_SHORT, "iget-short", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT = + new Info(Opcodes.IPUT, "iput", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_WIDE = + new Info(Opcodes.IPUT_WIDE, "iput-wide", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_OBJECT = + new Info(Opcodes.IPUT_OBJECT, "iput-object", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_BOOLEAN = + new Info(Opcodes.IPUT_BOOLEAN, "iput-boolean", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_BYTE = + new Info(Opcodes.IPUT_BYTE, "iput-byte", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_CHAR = + new Info(Opcodes.IPUT_CHAR, "iput-char", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info IPUT_SHORT = + new Info(Opcodes.IPUT_SHORT, "iput-short", + InstructionCodec.FORMAT_22C, IndexType.FIELD_REF); + + public static final Info SGET = + new Info(Opcodes.SGET, "sget", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_WIDE = + new Info(Opcodes.SGET_WIDE, "sget-wide", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_OBJECT = + new Info(Opcodes.SGET_OBJECT, "sget-object", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_BOOLEAN = + new Info(Opcodes.SGET_BOOLEAN, "sget-boolean", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_BYTE = + new Info(Opcodes.SGET_BYTE, "sget-byte", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_CHAR = + new Info(Opcodes.SGET_CHAR, "sget-char", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SGET_SHORT = + new Info(Opcodes.SGET_SHORT, "sget-short", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT = + new Info(Opcodes.SPUT, "sput", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_WIDE = + new Info(Opcodes.SPUT_WIDE, "sput-wide", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_OBJECT = + new Info(Opcodes.SPUT_OBJECT, "sput-object", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_BOOLEAN = + new Info(Opcodes.SPUT_BOOLEAN, "sput-boolean", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_BYTE = + new Info(Opcodes.SPUT_BYTE, "sput-byte", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_CHAR = + new Info(Opcodes.SPUT_CHAR, "sput-char", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info SPUT_SHORT = + new Info(Opcodes.SPUT_SHORT, "sput-short", + InstructionCodec.FORMAT_21C, IndexType.FIELD_REF); + + public static final Info INVOKE_VIRTUAL = + new Info(Opcodes.INVOKE_VIRTUAL, "invoke-virtual", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_SUPER = + new Info(Opcodes.INVOKE_SUPER, "invoke-super", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_DIRECT = + new Info(Opcodes.INVOKE_DIRECT, "invoke-direct", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_STATIC = + new Info(Opcodes.INVOKE_STATIC, "invoke-static", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_INTERFACE = + new Info(Opcodes.INVOKE_INTERFACE, "invoke-interface", + InstructionCodec.FORMAT_35C, IndexType.METHOD_REF); + + public static final Info INVOKE_VIRTUAL_RANGE = + new Info(Opcodes.INVOKE_VIRTUAL_RANGE, "invoke-virtual/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info INVOKE_SUPER_RANGE = + new Info(Opcodes.INVOKE_SUPER_RANGE, "invoke-super/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info INVOKE_DIRECT_RANGE = + new Info(Opcodes.INVOKE_DIRECT_RANGE, "invoke-direct/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info INVOKE_STATIC_RANGE = + new Info(Opcodes.INVOKE_STATIC_RANGE, "invoke-static/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info INVOKE_INTERFACE_RANGE = + new Info(Opcodes.INVOKE_INTERFACE_RANGE, "invoke-interface/range", + InstructionCodec.FORMAT_3RC, IndexType.METHOD_REF); + + public static final Info NEG_INT = + new Info(Opcodes.NEG_INT, "neg-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NOT_INT = + new Info(Opcodes.NOT_INT, "not-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NEG_LONG = + new Info(Opcodes.NEG_LONG, "neg-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NOT_LONG = + new Info(Opcodes.NOT_LONG, "not-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NEG_FLOAT = + new Info(Opcodes.NEG_FLOAT, "neg-float", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info NEG_DOUBLE = + new Info(Opcodes.NEG_DOUBLE, "neg-double", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_LONG = + new Info(Opcodes.INT_TO_LONG, "int-to-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_FLOAT = + new Info(Opcodes.INT_TO_FLOAT, "int-to-float", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_DOUBLE = + new Info(Opcodes.INT_TO_DOUBLE, "int-to-double", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info LONG_TO_INT = + new Info(Opcodes.LONG_TO_INT, "long-to-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info LONG_TO_FLOAT = + new Info(Opcodes.LONG_TO_FLOAT, "long-to-float", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info LONG_TO_DOUBLE = + new Info(Opcodes.LONG_TO_DOUBLE, "long-to-double", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info FLOAT_TO_INT = + new Info(Opcodes.FLOAT_TO_INT, "float-to-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info FLOAT_TO_LONG = + new Info(Opcodes.FLOAT_TO_LONG, "float-to-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info FLOAT_TO_DOUBLE = + new Info(Opcodes.FLOAT_TO_DOUBLE, "float-to-double", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DOUBLE_TO_INT = + new Info(Opcodes.DOUBLE_TO_INT, "double-to-int", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DOUBLE_TO_LONG = + new Info(Opcodes.DOUBLE_TO_LONG, "double-to-long", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DOUBLE_TO_FLOAT = + new Info(Opcodes.DOUBLE_TO_FLOAT, "double-to-float", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_BYTE = + new Info(Opcodes.INT_TO_BYTE, "int-to-byte", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_CHAR = + new Info(Opcodes.INT_TO_CHAR, "int-to-char", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info INT_TO_SHORT = + new Info(Opcodes.INT_TO_SHORT, "int-to-short", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_INT = + new Info(Opcodes.ADD_INT, "add-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SUB_INT = + new Info(Opcodes.SUB_INT, "sub-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info MUL_INT = + new Info(Opcodes.MUL_INT, "mul-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info DIV_INT = + new Info(Opcodes.DIV_INT, "div-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info REM_INT = + new Info(Opcodes.REM_INT, "rem-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AND_INT = + new Info(Opcodes.AND_INT, "and-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info OR_INT = + new Info(Opcodes.OR_INT, "or-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info XOR_INT = + new Info(Opcodes.XOR_INT, "xor-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SHL_INT = + new Info(Opcodes.SHL_INT, "shl-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SHR_INT = + new Info(Opcodes.SHR_INT, "shr-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info USHR_INT = + new Info(Opcodes.USHR_INT, "ushr-int", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info ADD_LONG = + new Info(Opcodes.ADD_LONG, "add-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SUB_LONG = + new Info(Opcodes.SUB_LONG, "sub-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info MUL_LONG = + new Info(Opcodes.MUL_LONG, "mul-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info DIV_LONG = + new Info(Opcodes.DIV_LONG, "div-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info REM_LONG = + new Info(Opcodes.REM_LONG, "rem-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info AND_LONG = + new Info(Opcodes.AND_LONG, "and-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info OR_LONG = + new Info(Opcodes.OR_LONG, "or-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info XOR_LONG = + new Info(Opcodes.XOR_LONG, "xor-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SHL_LONG = + new Info(Opcodes.SHL_LONG, "shl-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SHR_LONG = + new Info(Opcodes.SHR_LONG, "shr-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info USHR_LONG = + new Info(Opcodes.USHR_LONG, "ushr-long", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info ADD_FLOAT = + new Info(Opcodes.ADD_FLOAT, "add-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SUB_FLOAT = + new Info(Opcodes.SUB_FLOAT, "sub-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info MUL_FLOAT = + new Info(Opcodes.MUL_FLOAT, "mul-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info DIV_FLOAT = + new Info(Opcodes.DIV_FLOAT, "div-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info REM_FLOAT = + new Info(Opcodes.REM_FLOAT, "rem-float", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info ADD_DOUBLE = + new Info(Opcodes.ADD_DOUBLE, "add-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info SUB_DOUBLE = + new Info(Opcodes.SUB_DOUBLE, "sub-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info MUL_DOUBLE = + new Info(Opcodes.MUL_DOUBLE, "mul-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info DIV_DOUBLE = + new Info(Opcodes.DIV_DOUBLE, "div-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info REM_DOUBLE = + new Info(Opcodes.REM_DOUBLE, "rem-double", + InstructionCodec.FORMAT_23X, IndexType.NONE); + + public static final Info ADD_INT_2ADDR = + new Info(Opcodes.ADD_INT_2ADDR, "add-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SUB_INT_2ADDR = + new Info(Opcodes.SUB_INT_2ADDR, "sub-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MUL_INT_2ADDR = + new Info(Opcodes.MUL_INT_2ADDR, "mul-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DIV_INT_2ADDR = + new Info(Opcodes.DIV_INT_2ADDR, "div-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info REM_INT_2ADDR = + new Info(Opcodes.REM_INT_2ADDR, "rem-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info AND_INT_2ADDR = + new Info(Opcodes.AND_INT_2ADDR, "and-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info OR_INT_2ADDR = + new Info(Opcodes.OR_INT_2ADDR, "or-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info XOR_INT_2ADDR = + new Info(Opcodes.XOR_INT_2ADDR, "xor-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SHL_INT_2ADDR = + new Info(Opcodes.SHL_INT_2ADDR, "shl-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SHR_INT_2ADDR = + new Info(Opcodes.SHR_INT_2ADDR, "shr-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info USHR_INT_2ADDR = + new Info(Opcodes.USHR_INT_2ADDR, "ushr-int/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_LONG_2ADDR = + new Info(Opcodes.ADD_LONG_2ADDR, "add-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SUB_LONG_2ADDR = + new Info(Opcodes.SUB_LONG_2ADDR, "sub-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MUL_LONG_2ADDR = + new Info(Opcodes.MUL_LONG_2ADDR, "mul-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DIV_LONG_2ADDR = + new Info(Opcodes.DIV_LONG_2ADDR, "div-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info REM_LONG_2ADDR = + new Info(Opcodes.REM_LONG_2ADDR, "rem-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info AND_LONG_2ADDR = + new Info(Opcodes.AND_LONG_2ADDR, "and-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info OR_LONG_2ADDR = + new Info(Opcodes.OR_LONG_2ADDR, "or-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info XOR_LONG_2ADDR = + new Info(Opcodes.XOR_LONG_2ADDR, "xor-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SHL_LONG_2ADDR = + new Info(Opcodes.SHL_LONG_2ADDR, "shl-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SHR_LONG_2ADDR = + new Info(Opcodes.SHR_LONG_2ADDR, "shr-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info USHR_LONG_2ADDR = + new Info(Opcodes.USHR_LONG_2ADDR, "ushr-long/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_FLOAT_2ADDR = + new Info(Opcodes.ADD_FLOAT_2ADDR, "add-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SUB_FLOAT_2ADDR = + new Info(Opcodes.SUB_FLOAT_2ADDR, "sub-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MUL_FLOAT_2ADDR = + new Info(Opcodes.MUL_FLOAT_2ADDR, "mul-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DIV_FLOAT_2ADDR = + new Info(Opcodes.DIV_FLOAT_2ADDR, "div-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info REM_FLOAT_2ADDR = + new Info(Opcodes.REM_FLOAT_2ADDR, "rem-float/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_DOUBLE_2ADDR = + new Info(Opcodes.ADD_DOUBLE_2ADDR, "add-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info SUB_DOUBLE_2ADDR = + new Info(Opcodes.SUB_DOUBLE_2ADDR, "sub-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info MUL_DOUBLE_2ADDR = + new Info(Opcodes.MUL_DOUBLE_2ADDR, "mul-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info DIV_DOUBLE_2ADDR = + new Info(Opcodes.DIV_DOUBLE_2ADDR, "div-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info REM_DOUBLE_2ADDR = + new Info(Opcodes.REM_DOUBLE_2ADDR, "rem-double/2addr", + InstructionCodec.FORMAT_12X, IndexType.NONE); + + public static final Info ADD_INT_LIT16 = + new Info(Opcodes.ADD_INT_LIT16, "add-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info RSUB_INT = + new Info(Opcodes.RSUB_INT, "rsub-int", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info MUL_INT_LIT16 = + new Info(Opcodes.MUL_INT_LIT16, "mul-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info DIV_INT_LIT16 = + new Info(Opcodes.DIV_INT_LIT16, "div-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info REM_INT_LIT16 = + new Info(Opcodes.REM_INT_LIT16, "rem-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info AND_INT_LIT16 = + new Info(Opcodes.AND_INT_LIT16, "and-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info OR_INT_LIT16 = + new Info(Opcodes.OR_INT_LIT16, "or-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info XOR_INT_LIT16 = + new Info(Opcodes.XOR_INT_LIT16, "xor-int/lit16", + InstructionCodec.FORMAT_22S, IndexType.NONE); + + public static final Info ADD_INT_LIT8 = + new Info(Opcodes.ADD_INT_LIT8, "add-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info RSUB_INT_LIT8 = + new Info(Opcodes.RSUB_INT_LIT8, "rsub-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info MUL_INT_LIT8 = + new Info(Opcodes.MUL_INT_LIT8, "mul-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info DIV_INT_LIT8 = + new Info(Opcodes.DIV_INT_LIT8, "div-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info REM_INT_LIT8 = + new Info(Opcodes.REM_INT_LIT8, "rem-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info AND_INT_LIT8 = + new Info(Opcodes.AND_INT_LIT8, "and-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info OR_INT_LIT8 = + new Info(Opcodes.OR_INT_LIT8, "or-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info XOR_INT_LIT8 = + new Info(Opcodes.XOR_INT_LIT8, "xor-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info SHL_INT_LIT8 = + new Info(Opcodes.SHL_INT_LIT8, "shl-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info SHR_INT_LIT8 = + new Info(Opcodes.SHR_INT_LIT8, "shr-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info USHR_INT_LIT8 = + new Info(Opcodes.USHR_INT_LIT8, "ushr-int/lit8", + InstructionCodec.FORMAT_22B, IndexType.NONE); + + public static final Info INVOKE_POLYMORPHIC = + new Info(Opcodes.INVOKE_POLYMORPHIC, "invoke-polymorphic", + InstructionCodec.FORMAT_45CC, IndexType.METHOD_AND_PROTO_REF); + + public static final Info INVOKE_POLYMORPHIC_RANGE = + new Info(Opcodes.INVOKE_POLYMORPHIC_RANGE, "invoke-polymorphic/range", + InstructionCodec.FORMAT_4RCC, IndexType.METHOD_AND_PROTO_REF); + + public static final Info INVOKE_CUSTOM = + new Info(Opcodes.INVOKE_CUSTOM, "invoke-custom", + InstructionCodec.FORMAT_35C, IndexType.CALL_SITE_REF); + + public static final Info INVOKE_CUSTOM_RANGE = + new Info(Opcodes.INVOKE_CUSTOM_RANGE, "invoke-custom/range", + InstructionCodec.FORMAT_3RC, IndexType.CALL_SITE_REF); + + // END(opcode-info-defs) + + // Static initialization. + static { + INFO = new Info[Opcodes.MAX_VALUE - Opcodes.MIN_VALUE + 1]; + + // TODO: Stop using this constant. + set(SPECIAL_FORMAT); + + // TODO: These payload opcodes should be generated by opcode-gen. + set(PACKED_SWITCH_PAYLOAD); + set(SPARSE_SWITCH_PAYLOAD); + set(FILL_ARRAY_DATA_PAYLOAD); + + // BEGIN(opcode-info-init); GENERATED AUTOMATICALLY BY opcode-gen + set(NOP); + set(MOVE); + set(MOVE_FROM16); + set(MOVE_16); + set(MOVE_WIDE); + set(MOVE_WIDE_FROM16); + set(MOVE_WIDE_16); + set(MOVE_OBJECT); + set(MOVE_OBJECT_FROM16); + set(MOVE_OBJECT_16); + set(MOVE_RESULT); + set(MOVE_RESULT_WIDE); + set(MOVE_RESULT_OBJECT); + set(MOVE_EXCEPTION); + set(RETURN_VOID); + set(RETURN); + set(RETURN_WIDE); + set(RETURN_OBJECT); + set(CONST_4); + set(CONST_16); + set(CONST); + set(CONST_HIGH16); + set(CONST_WIDE_16); + set(CONST_WIDE_32); + set(CONST_WIDE); + set(CONST_WIDE_HIGH16); + set(CONST_STRING); + set(CONST_STRING_JUMBO); + set(CONST_CLASS); + set(MONITOR_ENTER); + set(MONITOR_EXIT); + set(CHECK_CAST); + set(INSTANCE_OF); + set(ARRAY_LENGTH); + set(NEW_INSTANCE); + set(NEW_ARRAY); + set(FILLED_NEW_ARRAY); + set(FILLED_NEW_ARRAY_RANGE); + set(FILL_ARRAY_DATA); + set(THROW); + set(GOTO); + set(GOTO_16); + set(GOTO_32); + set(PACKED_SWITCH); + set(SPARSE_SWITCH); + set(CMPL_FLOAT); + set(CMPG_FLOAT); + set(CMPL_DOUBLE); + set(CMPG_DOUBLE); + set(CMP_LONG); + set(IF_EQ); + set(IF_NE); + set(IF_LT); + set(IF_GE); + set(IF_GT); + set(IF_LE); + set(IF_EQZ); + set(IF_NEZ); + set(IF_LTZ); + set(IF_GEZ); + set(IF_GTZ); + set(IF_LEZ); + set(AGET); + set(AGET_WIDE); + set(AGET_OBJECT); + set(AGET_BOOLEAN); + set(AGET_BYTE); + set(AGET_CHAR); + set(AGET_SHORT); + set(APUT); + set(APUT_WIDE); + set(APUT_OBJECT); + set(APUT_BOOLEAN); + set(APUT_BYTE); + set(APUT_CHAR); + set(APUT_SHORT); + set(IGET); + set(IGET_WIDE); + set(IGET_OBJECT); + set(IGET_BOOLEAN); + set(IGET_BYTE); + set(IGET_CHAR); + set(IGET_SHORT); + set(IPUT); + set(IPUT_WIDE); + set(IPUT_OBJECT); + set(IPUT_BOOLEAN); + set(IPUT_BYTE); + set(IPUT_CHAR); + set(IPUT_SHORT); + set(SGET); + set(SGET_WIDE); + set(SGET_OBJECT); + set(SGET_BOOLEAN); + set(SGET_BYTE); + set(SGET_CHAR); + set(SGET_SHORT); + set(SPUT); + set(SPUT_WIDE); + set(SPUT_OBJECT); + set(SPUT_BOOLEAN); + set(SPUT_BYTE); + set(SPUT_CHAR); + set(SPUT_SHORT); + set(INVOKE_VIRTUAL); + set(INVOKE_SUPER); + set(INVOKE_DIRECT); + set(INVOKE_STATIC); + set(INVOKE_INTERFACE); + set(INVOKE_VIRTUAL_RANGE); + set(INVOKE_SUPER_RANGE); + set(INVOKE_DIRECT_RANGE); + set(INVOKE_STATIC_RANGE); + set(INVOKE_INTERFACE_RANGE); + set(NEG_INT); + set(NOT_INT); + set(NEG_LONG); + set(NOT_LONG); + set(NEG_FLOAT); + set(NEG_DOUBLE); + set(INT_TO_LONG); + set(INT_TO_FLOAT); + set(INT_TO_DOUBLE); + set(LONG_TO_INT); + set(LONG_TO_FLOAT); + set(LONG_TO_DOUBLE); + set(FLOAT_TO_INT); + set(FLOAT_TO_LONG); + set(FLOAT_TO_DOUBLE); + set(DOUBLE_TO_INT); + set(DOUBLE_TO_LONG); + set(DOUBLE_TO_FLOAT); + set(INT_TO_BYTE); + set(INT_TO_CHAR); + set(INT_TO_SHORT); + set(ADD_INT); + set(SUB_INT); + set(MUL_INT); + set(DIV_INT); + set(REM_INT); + set(AND_INT); + set(OR_INT); + set(XOR_INT); + set(SHL_INT); + set(SHR_INT); + set(USHR_INT); + set(ADD_LONG); + set(SUB_LONG); + set(MUL_LONG); + set(DIV_LONG); + set(REM_LONG); + set(AND_LONG); + set(OR_LONG); + set(XOR_LONG); + set(SHL_LONG); + set(SHR_LONG); + set(USHR_LONG); + set(ADD_FLOAT); + set(SUB_FLOAT); + set(MUL_FLOAT); + set(DIV_FLOAT); + set(REM_FLOAT); + set(ADD_DOUBLE); + set(SUB_DOUBLE); + set(MUL_DOUBLE); + set(DIV_DOUBLE); + set(REM_DOUBLE); + set(ADD_INT_2ADDR); + set(SUB_INT_2ADDR); + set(MUL_INT_2ADDR); + set(DIV_INT_2ADDR); + set(REM_INT_2ADDR); + set(AND_INT_2ADDR); + set(OR_INT_2ADDR); + set(XOR_INT_2ADDR); + set(SHL_INT_2ADDR); + set(SHR_INT_2ADDR); + set(USHR_INT_2ADDR); + set(ADD_LONG_2ADDR); + set(SUB_LONG_2ADDR); + set(MUL_LONG_2ADDR); + set(DIV_LONG_2ADDR); + set(REM_LONG_2ADDR); + set(AND_LONG_2ADDR); + set(OR_LONG_2ADDR); + set(XOR_LONG_2ADDR); + set(SHL_LONG_2ADDR); + set(SHR_LONG_2ADDR); + set(USHR_LONG_2ADDR); + set(ADD_FLOAT_2ADDR); + set(SUB_FLOAT_2ADDR); + set(MUL_FLOAT_2ADDR); + set(DIV_FLOAT_2ADDR); + set(REM_FLOAT_2ADDR); + set(ADD_DOUBLE_2ADDR); + set(SUB_DOUBLE_2ADDR); + set(MUL_DOUBLE_2ADDR); + set(DIV_DOUBLE_2ADDR); + set(REM_DOUBLE_2ADDR); + set(ADD_INT_LIT16); + set(RSUB_INT); + set(MUL_INT_LIT16); + set(DIV_INT_LIT16); + set(REM_INT_LIT16); + set(AND_INT_LIT16); + set(OR_INT_LIT16); + set(XOR_INT_LIT16); + set(ADD_INT_LIT8); + set(RSUB_INT_LIT8); + set(MUL_INT_LIT8); + set(DIV_INT_LIT8); + set(REM_INT_LIT8); + set(AND_INT_LIT8); + set(OR_INT_LIT8); + set(XOR_INT_LIT8); + set(SHL_INT_LIT8); + set(SHR_INT_LIT8); + set(USHR_INT_LIT8); + set(INVOKE_POLYMORPHIC); + set(INVOKE_POLYMORPHIC_RANGE); + set(INVOKE_CUSTOM); + set(INVOKE_CUSTOM_RANGE); + // END(opcode-info-init) + } + + /** + * This class is uninstantiable. + */ + private OpcodeInfo() { + // This space intentionally left blank. + } + + /** + * Gets the {@link Info} for the given opcode value. + * + * @param opcode {@code Opcodes.MIN_VALUE..Opcodes.MAX_VALUE;} the + * opcode value + * @return non-null; the associated opcode information instance + */ + public static Info get(int opcode) { + int idx = opcode - Opcodes.MIN_VALUE; + + try { + Info result = INFO[idx]; + if (result != null) { + return result; + } + } catch (ArrayIndexOutOfBoundsException ex) { + // Fall through. + } + + throw new IllegalArgumentException("bogus opcode: " + + Hex.u2or4(opcode)); + } + + /** + * Gets the name of the given opcode. + */ + public static String getName(int opcode) { + return get(opcode).getName(); + } + + /** + * Gets the format (an {@link InstructionCodec}) for the given opcode + * value. + */ + public static InstructionCodec getFormat(int opcode) { + return get(opcode).getFormat(); + } + + /** + * Gets the {@link IndexType} for the given opcode value. + */ + public static IndexType getIndexType(int opcode) { + return get(opcode).getIndexType(); + } + + /** + * Puts the given opcode into the table of all ops. + * + * @param opcode non-null; the opcode + */ + private static void set(Info opcode) { + int idx = opcode.getOpcode() - Opcodes.MIN_VALUE; + INFO[idx] = opcode; + } + + /** + * Information about an opcode. + */ + public static class Info { + private final int opcode; + private final String name; + private final InstructionCodec format; + private final IndexType indexType; + + public Info(int opcode, String name, InstructionCodec format, + IndexType indexType) { + this.opcode = opcode; + this.name = name; + this.format = format; + this.indexType = indexType; + } + + public int getOpcode() { + return opcode; + } + + public String getName() { + return name; + } + + public InstructionCodec getFormat() { + return format; + } + + public IndexType getIndexType() { + return indexType; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/Opcodes.java b/dexlib/src/main/java/com/android/dx/io/Opcodes.java new file mode 100644 index 000000000..9afee410b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/Opcodes.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io; + +/** + * All the Dalvik opcode value constants. See the related spec + * document for the meaning and instruction format of each opcode. + */ +public final class Opcodes { + /** + * pseudo-opcode used for nonstandard format payload "instructions". TODO: + * Retire this concept, and start treating the payload instructions + * more like the rest. + */ + public static final int SPECIAL_FORMAT = -1; + + /** + * pseudo-opcode used to indicate there is no next opcode; used + * in opcode chaining lists + */ + public static final int NO_NEXT = -1; + + /** minimum valid opcode value */ + public static final int MIN_VALUE = -1; + + /** maximum valid opcode value */ + public static final int MAX_VALUE = 0xffff; + + // BEGIN(opcodes); GENERATED AUTOMATICALLY BY opcode-gen + public static final int NOP = 0x00; + public static final int MOVE = 0x01; + public static final int MOVE_FROM16 = 0x02; + public static final int MOVE_16 = 0x03; + public static final int MOVE_WIDE = 0x04; + public static final int MOVE_WIDE_FROM16 = 0x05; + public static final int MOVE_WIDE_16 = 0x06; + public static final int MOVE_OBJECT = 0x07; + public static final int MOVE_OBJECT_FROM16 = 0x08; + public static final int MOVE_OBJECT_16 = 0x09; + public static final int MOVE_RESULT = 0x0a; + public static final int MOVE_RESULT_WIDE = 0x0b; + public static final int MOVE_RESULT_OBJECT = 0x0c; + public static final int MOVE_EXCEPTION = 0x0d; + public static final int RETURN_VOID = 0x0e; + public static final int RETURN = 0x0f; + public static final int RETURN_WIDE = 0x10; + public static final int RETURN_OBJECT = 0x11; + public static final int CONST_4 = 0x12; + public static final int CONST_16 = 0x13; + public static final int CONST = 0x14; + public static final int CONST_HIGH16 = 0x15; + public static final int CONST_WIDE_16 = 0x16; + public static final int CONST_WIDE_32 = 0x17; + public static final int CONST_WIDE = 0x18; + public static final int CONST_WIDE_HIGH16 = 0x19; + public static final int CONST_STRING = 0x1a; + public static final int CONST_STRING_JUMBO = 0x1b; + public static final int CONST_CLASS = 0x1c; + public static final int MONITOR_ENTER = 0x1d; + public static final int MONITOR_EXIT = 0x1e; + public static final int CHECK_CAST = 0x1f; + public static final int INSTANCE_OF = 0x20; + public static final int ARRAY_LENGTH = 0x21; + public static final int NEW_INSTANCE = 0x22; + public static final int NEW_ARRAY = 0x23; + public static final int FILLED_NEW_ARRAY = 0x24; + public static final int FILLED_NEW_ARRAY_RANGE = 0x25; + public static final int FILL_ARRAY_DATA = 0x26; + public static final int THROW = 0x27; + public static final int GOTO = 0x28; + public static final int GOTO_16 = 0x29; + public static final int GOTO_32 = 0x2a; + public static final int PACKED_SWITCH = 0x2b; + public static final int SPARSE_SWITCH = 0x2c; + public static final int CMPL_FLOAT = 0x2d; + public static final int CMPG_FLOAT = 0x2e; + public static final int CMPL_DOUBLE = 0x2f; + public static final int CMPG_DOUBLE = 0x30; + public static final int CMP_LONG = 0x31; + public static final int IF_EQ = 0x32; + public static final int IF_NE = 0x33; + public static final int IF_LT = 0x34; + public static final int IF_GE = 0x35; + public static final int IF_GT = 0x36; + public static final int IF_LE = 0x37; + public static final int IF_EQZ = 0x38; + public static final int IF_NEZ = 0x39; + public static final int IF_LTZ = 0x3a; + public static final int IF_GEZ = 0x3b; + public static final int IF_GTZ = 0x3c; + public static final int IF_LEZ = 0x3d; + public static final int AGET = 0x44; + public static final int AGET_WIDE = 0x45; + public static final int AGET_OBJECT = 0x46; + public static final int AGET_BOOLEAN = 0x47; + public static final int AGET_BYTE = 0x48; + public static final int AGET_CHAR = 0x49; + public static final int AGET_SHORT = 0x4a; + public static final int APUT = 0x4b; + public static final int APUT_WIDE = 0x4c; + public static final int APUT_OBJECT = 0x4d; + public static final int APUT_BOOLEAN = 0x4e; + public static final int APUT_BYTE = 0x4f; + public static final int APUT_CHAR = 0x50; + public static final int APUT_SHORT = 0x51; + public static final int IGET = 0x52; + public static final int IGET_WIDE = 0x53; + public static final int IGET_OBJECT = 0x54; + public static final int IGET_BOOLEAN = 0x55; + public static final int IGET_BYTE = 0x56; + public static final int IGET_CHAR = 0x57; + public static final int IGET_SHORT = 0x58; + public static final int IPUT = 0x59; + public static final int IPUT_WIDE = 0x5a; + public static final int IPUT_OBJECT = 0x5b; + public static final int IPUT_BOOLEAN = 0x5c; + public static final int IPUT_BYTE = 0x5d; + public static final int IPUT_CHAR = 0x5e; + public static final int IPUT_SHORT = 0x5f; + public static final int SGET = 0x60; + public static final int SGET_WIDE = 0x61; + public static final int SGET_OBJECT = 0x62; + public static final int SGET_BOOLEAN = 0x63; + public static final int SGET_BYTE = 0x64; + public static final int SGET_CHAR = 0x65; + public static final int SGET_SHORT = 0x66; + public static final int SPUT = 0x67; + public static final int SPUT_WIDE = 0x68; + public static final int SPUT_OBJECT = 0x69; + public static final int SPUT_BOOLEAN = 0x6a; + public static final int SPUT_BYTE = 0x6b; + public static final int SPUT_CHAR = 0x6c; + public static final int SPUT_SHORT = 0x6d; + public static final int INVOKE_VIRTUAL = 0x6e; + public static final int INVOKE_SUPER = 0x6f; + public static final int INVOKE_DIRECT = 0x70; + public static final int INVOKE_STATIC = 0x71; + public static final int INVOKE_INTERFACE = 0x72; + public static final int INVOKE_VIRTUAL_RANGE = 0x74; + public static final int INVOKE_SUPER_RANGE = 0x75; + public static final int INVOKE_DIRECT_RANGE = 0x76; + public static final int INVOKE_STATIC_RANGE = 0x77; + public static final int INVOKE_INTERFACE_RANGE = 0x78; + public static final int NEG_INT = 0x7b; + public static final int NOT_INT = 0x7c; + public static final int NEG_LONG = 0x7d; + public static final int NOT_LONG = 0x7e; + public static final int NEG_FLOAT = 0x7f; + public static final int NEG_DOUBLE = 0x80; + public static final int INT_TO_LONG = 0x81; + public static final int INT_TO_FLOAT = 0x82; + public static final int INT_TO_DOUBLE = 0x83; + public static final int LONG_TO_INT = 0x84; + public static final int LONG_TO_FLOAT = 0x85; + public static final int LONG_TO_DOUBLE = 0x86; + public static final int FLOAT_TO_INT = 0x87; + public static final int FLOAT_TO_LONG = 0x88; + public static final int FLOAT_TO_DOUBLE = 0x89; + public static final int DOUBLE_TO_INT = 0x8a; + public static final int DOUBLE_TO_LONG = 0x8b; + public static final int DOUBLE_TO_FLOAT = 0x8c; + public static final int INT_TO_BYTE = 0x8d; + public static final int INT_TO_CHAR = 0x8e; + public static final int INT_TO_SHORT = 0x8f; + public static final int ADD_INT = 0x90; + public static final int SUB_INT = 0x91; + public static final int MUL_INT = 0x92; + public static final int DIV_INT = 0x93; + public static final int REM_INT = 0x94; + public static final int AND_INT = 0x95; + public static final int OR_INT = 0x96; + public static final int XOR_INT = 0x97; + public static final int SHL_INT = 0x98; + public static final int SHR_INT = 0x99; + public static final int USHR_INT = 0x9a; + public static final int ADD_LONG = 0x9b; + public static final int SUB_LONG = 0x9c; + public static final int MUL_LONG = 0x9d; + public static final int DIV_LONG = 0x9e; + public static final int REM_LONG = 0x9f; + public static final int AND_LONG = 0xa0; + public static final int OR_LONG = 0xa1; + public static final int XOR_LONG = 0xa2; + public static final int SHL_LONG = 0xa3; + public static final int SHR_LONG = 0xa4; + public static final int USHR_LONG = 0xa5; + public static final int ADD_FLOAT = 0xa6; + public static final int SUB_FLOAT = 0xa7; + public static final int MUL_FLOAT = 0xa8; + public static final int DIV_FLOAT = 0xa9; + public static final int REM_FLOAT = 0xaa; + public static final int ADD_DOUBLE = 0xab; + public static final int SUB_DOUBLE = 0xac; + public static final int MUL_DOUBLE = 0xad; + public static final int DIV_DOUBLE = 0xae; + public static final int REM_DOUBLE = 0xaf; + public static final int ADD_INT_2ADDR = 0xb0; + public static final int SUB_INT_2ADDR = 0xb1; + public static final int MUL_INT_2ADDR = 0xb2; + public static final int DIV_INT_2ADDR = 0xb3; + public static final int REM_INT_2ADDR = 0xb4; + public static final int AND_INT_2ADDR = 0xb5; + public static final int OR_INT_2ADDR = 0xb6; + public static final int XOR_INT_2ADDR = 0xb7; + public static final int SHL_INT_2ADDR = 0xb8; + public static final int SHR_INT_2ADDR = 0xb9; + public static final int USHR_INT_2ADDR = 0xba; + public static final int ADD_LONG_2ADDR = 0xbb; + public static final int SUB_LONG_2ADDR = 0xbc; + public static final int MUL_LONG_2ADDR = 0xbd; + public static final int DIV_LONG_2ADDR = 0xbe; + public static final int REM_LONG_2ADDR = 0xbf; + public static final int AND_LONG_2ADDR = 0xc0; + public static final int OR_LONG_2ADDR = 0xc1; + public static final int XOR_LONG_2ADDR = 0xc2; + public static final int SHL_LONG_2ADDR = 0xc3; + public static final int SHR_LONG_2ADDR = 0xc4; + public static final int USHR_LONG_2ADDR = 0xc5; + public static final int ADD_FLOAT_2ADDR = 0xc6; + public static final int SUB_FLOAT_2ADDR = 0xc7; + public static final int MUL_FLOAT_2ADDR = 0xc8; + public static final int DIV_FLOAT_2ADDR = 0xc9; + public static final int REM_FLOAT_2ADDR = 0xca; + public static final int ADD_DOUBLE_2ADDR = 0xcb; + public static final int SUB_DOUBLE_2ADDR = 0xcc; + public static final int MUL_DOUBLE_2ADDR = 0xcd; + public static final int DIV_DOUBLE_2ADDR = 0xce; + public static final int REM_DOUBLE_2ADDR = 0xcf; + public static final int ADD_INT_LIT16 = 0xd0; + public static final int RSUB_INT = 0xd1; + public static final int MUL_INT_LIT16 = 0xd2; + public static final int DIV_INT_LIT16 = 0xd3; + public static final int REM_INT_LIT16 = 0xd4; + public static final int AND_INT_LIT16 = 0xd5; + public static final int OR_INT_LIT16 = 0xd6; + public static final int XOR_INT_LIT16 = 0xd7; + public static final int ADD_INT_LIT8 = 0xd8; + public static final int RSUB_INT_LIT8 = 0xd9; + public static final int MUL_INT_LIT8 = 0xda; + public static final int DIV_INT_LIT8 = 0xdb; + public static final int REM_INT_LIT8 = 0xdc; + public static final int AND_INT_LIT8 = 0xdd; + public static final int OR_INT_LIT8 = 0xde; + public static final int XOR_INT_LIT8 = 0xdf; + public static final int SHL_INT_LIT8 = 0xe0; + public static final int SHR_INT_LIT8 = 0xe1; + public static final int USHR_INT_LIT8 = 0xe2; + public static final int INVOKE_POLYMORPHIC = 0xfa; + public static final int INVOKE_POLYMORPHIC_RANGE = 0xfb; + public static final int INVOKE_CUSTOM = 0xfc; + public static final int INVOKE_CUSTOM_RANGE = 0xfd; + // END(opcodes) + + // TODO: Generate these payload opcodes with opcode-gen. + + /** + * special pseudo-opcode value for packed-switch data payload + * instructions + */ + public static final int PACKED_SWITCH_PAYLOAD = 0x100; + + /** special pseudo-opcode value for packed-switch data payload + * instructions + */ + public static final int SPARSE_SWITCH_PAYLOAD = 0x200; + + /** special pseudo-opcode value for fill-array-data data payload + * instructions + */ + public static final int FILL_ARRAY_DATA_PAYLOAD = 0x300; + + /** + * This class is uninstantiable. + */ + private Opcodes() { + // This space intentionally left blank. + } + + /** + * Determines if the given opcode has the right "shape" to be + * valid. This includes the range {@code 0x01..0xfe}, the range + * {@code 0x00ff..0xffff} where the low-order byte is either + * {@code 0} or {@code 0xff}, and the special opcode values {@code + * SPECIAL_FORMAT} and {@code NO_NEXT}. Note that not all of the + * opcode values that pass this test are in fact used. This method + * is meant to perform a quick check to reject blatantly wrong + * values (e.g. when validating arguments). + * + * @param opcode the opcode value + * @return {@code true} iff the value has the right "shape" to be + * possibly valid + */ + public static boolean isValidShape(int opcode) { + /* + * Note: This method bakes in knowledge that all opcodes are + * one of the forms: + * + * * single byte in range 0x01..0xfe -- normal opcodes + * * (byteValue << 8) -- nop and data payload opcodes + * * ((byteValue << 8) | 0xff) -- 16-bit extended opcodes + * * SPECIAL_FORMAT or NO_NEXT -- pseudo-opcodes + */ + + // Note: SPECIAL_FORMAT == NO_NEXT. + if (opcode < SPECIAL_FORMAT) { + return false; + } else if (opcode == SPECIAL_FORMAT) { + return true; + } + + int lowByte = opcode & 0xff; + if ((lowByte == 0) || (lowByte == 0xff)) { + return true; + } + + return (opcode & 0xff00) == 0; + } + + /** + * Gets the opcode out of an opcode unit, the latter of which may also + * include one or more argument values. + * + * @param opcodeUnit the opcode-containing code unit + * @return the extracted opcode + */ + public static int extractOpcodeFromUnit(int opcodeUnit) { + /* + * Note: This method bakes in knowledge that all opcodes are + * either single-byte or of the forms (byteValue << 8) or + * ((byteValue << 8) | 0xff). + */ + + int lowByte = opcodeUnit & 0xff; + return ((lowByte == 0) || (lowByte == 0xff)) ? opcodeUnit : lowByte; + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/AddressMap.java b/dexlib/src/main/java/com/android/dx/io/instructions/AddressMap.java new file mode 100644 index 000000000..953bdc915 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/AddressMap.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import java.util.HashMap; + +/** + * Map from addresses to addresses, where addresses are all + * {@code int}s. + */ +public final class AddressMap { + /** underlying map. TODO: This might be too inefficient. */ + private final HashMap map; + + /** + * Constructs an instance. + */ + public AddressMap() { + map = new HashMap(); + } + + /** + * Gets the value address corresponding to the given key address. Returns + * {@code -1} if there is no mapping. + */ + public int get(int keyAddress) { + Integer value = map.get(keyAddress); + return (value == null) ? -1 : value; + } + + /** + * Sets the value address associated with the given key address. + */ + public void put(int keyAddress, int valueAddress) { + map.put(keyAddress, valueAddress); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/BaseCodeCursor.java b/dexlib/src/main/java/com/android/dx/io/instructions/BaseCodeCursor.java new file mode 100644 index 000000000..a5eec5321 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/BaseCodeCursor.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +/** + * Base implementation of {@link CodeCursor}. + */ +public abstract class BaseCodeCursor implements CodeCursor { + /** base address map */ + private final AddressMap baseAddressMap; + + /** next index within {@link #array} to read from or write to */ + private int cursor; + + /** + * Constructs an instance. + */ + public BaseCodeCursor() { + this.baseAddressMap = new AddressMap(); + this.cursor = 0; + } + + /** {@inheritDoc} */ + public final int cursor() { + return cursor; + } + + /** {@inheritDoc} */ + public final int baseAddressForCursor() { + int mapped = baseAddressMap.get(cursor); + return (mapped >= 0) ? mapped : cursor; + } + + /** {@inheritDoc} */ + public final void setBaseAddress(int targetAddress, int baseAddress) { + baseAddressMap.put(targetAddress, baseAddress); + } + + /** + * Advance the cursor by the indicated amount. + */ + protected final void advance(int amount) { + cursor += amount; + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/CodeCursor.java b/dexlib/src/main/java/com/android/dx/io/instructions/CodeCursor.java new file mode 100644 index 000000000..de29e30d5 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/CodeCursor.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +/** + * Cursor over code units, for reading or writing out Dalvik bytecode. + */ +public interface CodeCursor { + /** + * Gets the cursor. The cursor is the offset in code units from + * the start of the input of the next code unit to be read or + * written, where the input generally consists of the code for a + * single method. + */ + public int cursor(); + + /** + * Gets the base address associated with the current cursor. This + * differs from the cursor value when explicitly set (by {@link + * #setBaseAddress}). This is used, in particular, to convey base + * addresses to switch data payload instructions, whose relative + * addresses are relative to the address of a dependant switch + * instruction. + */ + public int baseAddressForCursor(); + + /** + * Sets the base address for the given target address to be as indicated. + * + * @see #baseAddressForCursor + */ + public void setBaseAddress(int targetAddress, int baseAddress); +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/CodeInput.java b/dexlib/src/main/java/com/android/dx/io/instructions/CodeInput.java new file mode 100644 index 000000000..41a5ef7f8 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/CodeInput.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import java.io.EOFException; + +/** + * Input stream of code units, for reading in Dalvik bytecode. + */ +public interface CodeInput extends CodeCursor { + /** + * Returns whether there are any more code units to read. This + * is analogous to {@code hasNext()} on an interator. + */ + public boolean hasMore(); + + /** + * Reads a code unit. + */ + public int read() throws EOFException; + + /** + * Reads two code units, treating them as a little-endian {@code int}. + */ + public int readInt() throws EOFException; + + /** + * Reads four code units, treating them as a little-endian {@code long}. + */ + public long readLong() throws EOFException; +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/CodeOutput.java b/dexlib/src/main/java/com/android/dx/io/instructions/CodeOutput.java new file mode 100644 index 000000000..7d0077e04 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/CodeOutput.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +/** + * Output stream of code units, for writing out Dalvik bytecode. + */ +public interface CodeOutput extends CodeCursor { + /** + * Writes a code unit. + */ + public void write(short codeUnit); + + /** + * Writes two code units. + */ + public void write(short u0, short u1); + + /** + * Writes three code units. + */ + public void write(short u0, short u1, short u2); + + /** + * Writes four code units. + */ + public void write(short u0, short u1, short u2, short u3); + + /** + * Writes five code units. + */ + public void write(short u0, short u1, short u2, short u3, short u4); + + /** + * Writes an {@code int}, little-endian. + */ + public void writeInt(int value); + + /** + * Writes a {@code long}, little-endian. + */ + public void writeLong(long value); + + /** + * Writes the contents of the given array. + */ + public void write(byte[] data); + + /** + * Writes the contents of the given array. + */ + public void write(short[] data); + + /** + * Writes the contents of the given array. + */ + public void write(int[] data); + + /** + * Writes the contents of the given array. + */ + public void write(long[] data); +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/DecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/DecodedInstruction.java new file mode 100644 index 000000000..e5b70734e --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/DecodedInstruction.java @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import com.android.dex.DexException; +import com.android.dx.io.IndexType; +import com.android.dx.io.OpcodeInfo; +import com.android.dx.io.Opcodes; +import com.android.dx.util.Hex; +import java.io.EOFException; + +/** + * A decoded Dalvik instruction. This consists of a format codec, a + * numeric opcode, an optional index type, and any additional + * arguments of the instruction. The additional arguments (if any) are + * represented as uninterpreted data. + * + *

Note: The names of the arguments are not meant to + * match the names given in the Dalvik instruction format + * specification, specification which just names fields (somewhat) + * arbitrarily alphabetically from A. In this class, non-register + * fields are given descriptive names and register fields are + * consistently named alphabetically.

+ */ +public abstract class DecodedInstruction { + /** non-null; instruction format / codec */ + private final InstructionCodec format; + + /** opcode number */ + private final int opcode; + + /** constant index argument */ + private final int index; + + /** null-ok; index type */ + private final IndexType indexType; + + /** + * target address argument. This is an absolute address, not just + * a signed offset. Note: The address is unsigned, even + * though it is stored in an {@code int}. + */ + private final int target; + + /** + * literal value argument; also used for special verification error + * constants (format 20bc) as well as should-be-zero values + * (formats 10x, 20t, 30t, and 32x) + */ + private final long literal; + + /** + * Decodes an instruction from the given input source. + */ + public static DecodedInstruction decode(CodeInput in) throws EOFException { + int opcodeUnit = in.read(); + int opcode = Opcodes.extractOpcodeFromUnit(opcodeUnit); + InstructionCodec format = OpcodeInfo.getFormat(opcode); + + return format.decode(opcodeUnit, in); + } + + /** + * Decodes an array of instructions. The result has non-null + * elements at each offset that represents the start of an + * instruction. + */ + public static DecodedInstruction[] decodeAll(short[] encodedInstructions) { + int size = encodedInstructions.length; + DecodedInstruction[] decoded = new DecodedInstruction[size]; + ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions); + + try { + while (in.hasMore()) { + decoded[in.cursor()] = DecodedInstruction.decode(in); + } + } catch (EOFException ex) { + throw new DexException(ex); + } + + return decoded; + } + + /** + * Constructs an instance. + */ + public DecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal) { + if (format == null) { + throw new NullPointerException("format == null"); + } + + if (!Opcodes.isValidShape(opcode)) { + throw new IllegalArgumentException("invalid opcode"); + } + + this.format = format; + this.opcode = opcode; + this.index = index; + this.indexType = indexType; + this.target = target; + this.literal = literal; + } + + public final InstructionCodec getFormat() { + return format; + } + + public final int getOpcode() { + return opcode; + } + + /** + * Gets the opcode, as a code unit. + */ + public final short getOpcodeUnit() { + return (short) opcode; + } + + public final int getIndex() { + return index; + } + + /** + * Gets the index, as a code unit. + */ + public final short getIndexUnit() { + return (short) index; + } + + public final IndexType getIndexType() { + return indexType; + } + + /** + * Gets the raw target. + */ + public final int getTarget() { + return target; + } + + /** + * Gets the target as a relative offset from the given address. + */ + public final int getTarget(int baseAddress) { + return target - baseAddress; + } + + /** + * Gets the target as a relative offset from the given base + * address, as a code unit. This will throw if the value is out of + * the range of a signed code unit. + */ + public final short getTargetUnit(int baseAddress) { + int relativeTarget = getTarget(baseAddress); + + if (relativeTarget != (short) relativeTarget) { + throw new DexException("Target out of range: " + + Hex.s4(relativeTarget)); + } + + return (short) relativeTarget; + } + + /** + * Gets the target as a relative offset from the given base + * address, masked to be a byte in size. This will throw if the + * value is out of the range of a signed byte. + */ + public final int getTargetByte(int baseAddress) { + int relativeTarget = getTarget(baseAddress); + + if (relativeTarget != (byte) relativeTarget) { + throw new DexException("Target out of range: " + + Hex.s4(relativeTarget)); + } + + return relativeTarget & 0xff; + } + + public final long getLiteral() { + return literal; + } + + /** + * Gets the literal value, masked to be an int in size. This will + * throw if the value is out of the range of a signed int. + */ + public final int getLiteralInt() { + if (literal != (int) literal) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (int) literal; + } + + /** + * Gets the literal value, as a code unit. This will throw if the + * value is out of the range of a signed code unit. + */ + public final short getLiteralUnit() { + if (literal != (short) literal) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (short) literal; + } + + /** + * Gets the literal value, masked to be a byte in size. This will + * throw if the value is out of the range of a signed byte. + */ + public final int getLiteralByte() { + if (literal != (byte) literal) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (int) literal & 0xff; + } + + /** + * Gets the literal value, masked to be a nibble in size. This + * will throw if the value is out of the range of a signed nibble. + */ + public final int getLiteralNibble() { + if ((literal < -8) || (literal > 7)) { + throw new DexException("Literal out of range: " + Hex.u8(literal)); + } + + return (int) literal & 0xf; + } + + public abstract int getRegisterCount(); + + public int getA() { + return 0; + } + + public int getB() { + return 0; + } + + public int getC() { + return 0; + } + + public int getD() { + return 0; + } + + public int getE() { + return 0; + } + + /** + * Gets the register count, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getRegisterCountUnit() { + int registerCount = getRegisterCount(); + + if ((registerCount & ~0xffff) != 0) { + throw new DexException("Register count out of range: " + + Hex.u8(registerCount)); + } + + return (short) registerCount; + } + + /** + * Gets the A register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getAUnit() { + int a = getA(); + + if ((a & ~0xffff) != 0) { + throw new DexException("Register A out of range: " + Hex.u8(a)); + } + + return (short) a; + } + + /** + * Gets the A register number, as a byte. This will throw if the + * value is out of the range of an unsigned byte. + */ + public final short getAByte() { + int a = getA(); + + if ((a & ~0xff) != 0) { + throw new DexException("Register A out of range: " + Hex.u8(a)); + } + + return (short) a; + } + + /** + * Gets the A register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getANibble() { + int a = getA(); + + if ((a & ~0xf) != 0) { + throw new DexException("Register A out of range: " + Hex.u8(a)); + } + + return (short) a; + } + + /** + * Gets the B register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getBUnit() { + int b = getB(); + + if ((b & ~0xffff) != 0) { + throw new DexException("Register B out of range: " + Hex.u8(b)); + } + + return (short) b; + } + + /** + * Gets the B register number, as a byte. This will throw if the + * value is out of the range of an unsigned byte. + */ + public final short getBByte() { + int b = getB(); + + if ((b & ~0xff) != 0) { + throw new DexException("Register B out of range: " + Hex.u8(b)); + } + + return (short) b; + } + + /** + * Gets the B register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getBNibble() { + int b = getB(); + + if ((b & ~0xf) != 0) { + throw new DexException("Register B out of range: " + Hex.u8(b)); + } + + return (short) b; + } + + /** + * Gets the C register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getCUnit() { + int c = getC(); + + if ((c & ~0xffff) != 0) { + throw new DexException("Register C out of range: " + Hex.u8(c)); + } + + return (short) c; + } + + /** + * Gets the C register number, as a byte. This will throw if the + * value is out of the range of an unsigned byte. + */ + public final short getCByte() { + int c = getC(); + + if ((c & ~0xff) != 0) { + throw new DexException("Register C out of range: " + Hex.u8(c)); + } + + return (short) c; + } + + /** + * Gets the C register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getCNibble() { + int c = getC(); + + if ((c & ~0xf) != 0) { + throw new DexException("Register C out of range: " + Hex.u8(c)); + } + + return (short) c; + } + + /** + * Gets the D register number, as a code unit. This will throw if the + * value is out of the range of an unsigned code unit. + */ + public final short getDUnit() { + int d = getD(); + + if ((d & ~0xffff) != 0) { + throw new DexException("Register D out of range: " + Hex.u8(d)); + } + + return (short) d; + } + + /** + * Gets the D register number, as a byte. This will throw if the + * value is out of the range of an unsigned byte. + */ + public final short getDByte() { + int d = getD(); + + if ((d & ~0xff) != 0) { + throw new DexException("Register D out of range: " + Hex.u8(d)); + } + + return (short) d; + } + + /** + * Gets the D register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getDNibble() { + int d = getD(); + + if ((d & ~0xf) != 0) { + throw new DexException("Register D out of range: " + Hex.u8(d)); + } + + return (short) d; + } + + /** + * Gets the E register number, as a nibble. This will throw if the + * value is out of the range of an unsigned nibble. + */ + public final short getENibble() { + int e = getE(); + + if ((e & ~0xf) != 0) { + throw new DexException("Register E out of range: " + Hex.u8(e)); + } + + return (short) e; + } + + /** + * Encodes this instance to the given output. + */ + public final void encode(CodeOutput out) { + format.encode(this, out); + } + + /** + * Returns an instance just like this one, except with the index replaced + * with the given one. + */ + public abstract DecodedInstruction withIndex(int newIndex); +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/FillArrayDataPayloadDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/FillArrayDataPayloadDecodedInstruction.java new file mode 100644 index 000000000..6c4234e53 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/FillArrayDataPayloadDecodedInstruction.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +/** + * A decoded Dalvik instruction which contains the payload for + * a {@code packed-switch} instruction. + */ +public final class FillArrayDataPayloadDecodedInstruction + extends DecodedInstruction { + /** data array */ + private final Object data; + + /** number of elements */ + private final int size; + + /** element width */ + private final int elementWidth; + + /** + * Constructs an instance. This private instance doesn't check the + * type of the data array. + */ + private FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, Object data, int size, int elementWidth) { + super(format, opcode, 0, null, 0, 0L); + + this.data = data; + this.size = size; + this.elementWidth = elementWidth; + } + + /** + * Constructs an instance. + */ + public FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, byte[] data) { + this(format, opcode, data, data.length, 1); + } + + /** + * Constructs an instance. + */ + public FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, short[] data) { + this(format, opcode, data, data.length, 2); + } + + /** + * Constructs an instance. + */ + public FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, int[] data) { + this(format, opcode, data, data.length, 4); + } + + /** + * Constructs an instance. + */ + public FillArrayDataPayloadDecodedInstruction(InstructionCodec format, + int opcode, long[] data) { + this(format, opcode, data, data.length, 8); + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return 0; + } + + public short getElementWidthUnit() { + return (short) elementWidth; + } + + public int getSize() { + return size; + } + + public Object getData() { + return data; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + throw new UnsupportedOperationException("no index in instruction"); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/FiveRegisterDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/FiveRegisterDecodedInstruction.java new file mode 100644 index 000000000..f36951c67 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/FiveRegisterDecodedInstruction.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import com.android.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has five register arguments. + */ +public final class FiveRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register argument "B" */ + private final int b; + + /** register argument "C" */ + private final int c; + + /** register argument "D" */ + private final int d; + + /** register argument "E" */ + private final int e; + + /** + * Constructs an instance. + */ + public FiveRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int b, int c, int d, int e) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return 5; + } + + /** {@inheritDoc} */ + public int getA() { + return a; + } + + /** {@inheritDoc} */ + public int getB() { + return b; + } + + /** {@inheritDoc} */ + public int getC() { + return c; + } + + /** {@inheritDoc} */ + public int getD() { + return d; + } + + /** {@inheritDoc} */ + public int getE() { + return e; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + return new FiveRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, b, c, d, e); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/FourRegisterDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/FourRegisterDecodedInstruction.java new file mode 100644 index 000000000..234e37066 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/FourRegisterDecodedInstruction.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import com.android.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has five register arguments. + */ +public final class FourRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register argument "B" */ + private final int b; + + /** register argument "C" */ + private final int c; + + /** register argument "D" */ + private final int d; + + /** + * Constructs an instance. + */ + public FourRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int b, int c, int d) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return 4; + } + + /** {@inheritDoc} */ + public int getA() { + return a; + } + + /** {@inheritDoc} */ + public int getB() { + return b; + } + + /** {@inheritDoc} */ + public int getC() { + return c; + } + + /** {@inheritDoc} */ + public int getD() { + return d; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + return new FourRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, b, c, d); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/InstructionCodec.java b/dexlib/src/main/java/com/android/dx/io/instructions/InstructionCodec.java new file mode 100644 index 000000000..9c305757b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/InstructionCodec.java @@ -0,0 +1,987 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import com.android.dex.DexException; +import com.android.dx.io.IndexType; +import com.android.dx.io.OpcodeInfo; +import com.android.dx.io.Opcodes; +import com.android.dx.util.Hex; +import java.io.EOFException; + +/** + * Representation of an instruction format, which knows how to decode into + * and encode from instances of {@link DecodedInstruction}. + */ +public enum InstructionCodec { + FORMAT_00X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return new ZeroRegisterDecodedInstruction( + this, opcodeUnit, 0, null, + 0, 0L); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write(insn.getOpcodeUnit()); + } + }, + + FORMAT_10X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); // should be zero + return new ZeroRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write(insn.getOpcodeUnit()); + } + }, + + FORMAT_12X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcodeUnit(), + makeByte(insn.getA(), insn.getB()))); + } + }, + + FORMAT_11N() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int literal = (nibble3(opcodeUnit) << 28) >> 28; // sign-extend + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcodeUnit(), + makeByte(insn.getA(), insn.getLiteralNibble()))); + } + }, + + FORMAT_11X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write(codeUnit(insn.getOpcode(), insn.getA())); + } + }, + + FORMAT_10T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int target = (byte) byte1(opcodeUnit); // sign-extend + return new ZeroRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, 0L); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int relativeTarget = insn.getTargetByte(out.cursor()); + out.write(codeUnit(insn.getOpcode(), relativeTarget)); + } + }, + + FORMAT_20T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); // should be zero + int target = (short) in.read(); // sign-extend + return new ZeroRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, literal); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + short relativeTarget = insn.getTargetUnit(out.cursor()); + out.write(insn.getOpcodeUnit(), relativeTarget); + } + }, + + FORMAT_20BC() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + // Note: We use the literal field to hold the decoded AA value. + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); + int index = in.read(); + return new ZeroRegisterDecodedInstruction( + this, opcode, index, IndexType.VARIES, + 0, literal); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getLiteralByte()), + insn.getIndexUnit()); + } + }, + + FORMAT_22X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int b = in.read(); + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + insn.getBUnit()); + } + }, + + FORMAT_21T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int target = (short) in.read(); // sign-extend + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + short relativeTarget = insn.getTargetUnit(out.cursor()); + out.write(codeUnit(insn.getOpcode(), insn.getA()), relativeTarget); + } + }, + + FORMAT_21S() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int literal = (short) in.read(); // sign-extend + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + insn.getLiteralUnit()); + } + }, + + FORMAT_21H() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + long literal = (short) in.read(); // sign-extend + + /* + * Format 21h decodes differently depending on the opcode, + * because the "signed hat" might represent either a 32- + * or 64- bit value. + */ + literal <<= (opcode == Opcodes.CONST_HIGH16) ? 16 : 48; + + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + // See above. + int opcode = insn.getOpcode(); + int shift = (opcode == Opcodes.CONST_HIGH16) ? 16 : 48; + short literal = (short) (insn.getLiteral() >> shift); + + out.write(codeUnit(opcode, insn.getA()), literal); + } + }, + + FORMAT_21C() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int index = in.read(); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + return new OneRegisterDecodedInstruction( + this, opcode, index, indexType, + 0, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + insn.getIndexUnit()); + } + }, + + FORMAT_23X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int bc = in.read(); + int b = byte0(bc); + int c = byte1(bc); + return new ThreeRegisterDecodedInstruction( + this, opcode, 0, null, + 0, 0L, + a, b, c); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + codeUnit(insn.getB(), insn.getC())); + } + }, + + FORMAT_22B() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int bc = in.read(); + int b = byte0(bc); + int literal = (byte) byte1(bc); // sign-extend + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + codeUnit(insn.getB(), + insn.getLiteralByte())); + } + }, + + FORMAT_22T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + int target = (short) in.read(); // sign-extend + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + short relativeTarget = insn.getTargetUnit(out.cursor()); + out.write( + codeUnit(insn.getOpcode(), + makeByte(insn.getA(), insn.getB())), + relativeTarget); + } + }, + + FORMAT_22S() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + int literal = (short) in.read(); // sign-extend + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), + makeByte(insn.getA(), insn.getB())), + insn.getLiteralUnit()); + } + }, + + FORMAT_22C() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + int index = in.read(); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + return new TwoRegisterDecodedInstruction( + this, opcode, index, indexType, + 0, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), + makeByte(insn.getA(), insn.getB())), + insn.getIndexUnit()); + } + }, + + FORMAT_22CS() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = nibble2(opcodeUnit); + int b = nibble3(opcodeUnit); + int index = in.read(); + return new TwoRegisterDecodedInstruction( + this, opcode, index, IndexType.FIELD_OFFSET, + 0, 0L, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write( + codeUnit(insn.getOpcode(), + makeByte(insn.getA(), insn.getB())), + insn.getIndexUnit()); + } + }, + + FORMAT_30T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); // should be zero + int target = in.readInt(); + return new ZeroRegisterDecodedInstruction( + this, opcode, 0, null, + baseAddress + target, literal); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int relativeTarget = insn.getTarget(out.cursor()); + out.write(insn.getOpcodeUnit(), + unit0(relativeTarget), unit1(relativeTarget)); + } + }, + + FORMAT_32X() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int literal = byte1(opcodeUnit); // should be zero + int a = in.read(); + int b = in.read(); + return new TwoRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a, b); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + out.write(insn.getOpcodeUnit(), insn.getAUnit(), insn.getBUnit()); + } + }, + + FORMAT_31I() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int literal = in.readInt(); + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int literal = insn.getLiteralInt(); + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + unit0(literal), + unit1(literal)); + } + }, + + FORMAT_31T() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.cursor() - 1; + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int target = baseAddress + in.readInt(); + + /* + * Switch instructions need to "forward" their addresses to their + * payload target instructions. + */ + switch (opcode) { + case Opcodes.PACKED_SWITCH: + case Opcodes.SPARSE_SWITCH: { + in.setBaseAddress(target, baseAddress); + break; + } + } + + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + target, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int relativeTarget = insn.getTarget(out.cursor()); + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + unit0(relativeTarget), unit1(relativeTarget)); + } + }, + + FORMAT_31C() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + int index = in.readInt(); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + return new OneRegisterDecodedInstruction( + this, opcode, index, indexType, + 0, 0L, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + int index = insn.getIndex(); + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + unit0(index), + unit1(index)); + } + }, + + FORMAT_35C() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterList(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterList(insn, out); + } + }, + + FORMAT_35MS() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterList(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterList(insn, out); + } + }, + + FORMAT_35MI() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterList(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterList(insn, out); + } + }, + + FORMAT_3RC() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterRange(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterRange(insn, out); + } + }, + + FORMAT_3RMS() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterRange(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterRange(insn, out); + } + }, + + FORMAT_3RMI() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterRange(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterRange(insn, out); + } + }, + + FORMAT_51L() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int opcode = byte0(opcodeUnit); + int a = byte1(opcodeUnit); + long literal = in.readLong(); + return new OneRegisterDecodedInstruction( + this, opcode, 0, null, + 0, literal, + a); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + long literal = insn.getLiteral(); + out.write( + codeUnit(insn.getOpcode(), insn.getA()), + unit0(literal), + unit1(literal), + unit2(literal), + unit3(literal)); + } + }, + + FORMAT_45CC() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterList(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterList(insn, out); + } + }, + + FORMAT_4RCC() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + return decodeRegisterList(this, opcodeUnit, in); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + encodeRegisterList(insn, out); + } + }, + + FORMAT_PACKED_SWITCH_PAYLOAD() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.baseAddressForCursor() - 1; // already read opcode + int size = in.read(); + int firstKey = in.readInt(); + int[] targets = new int[size]; + + for (int i = 0; i < size; i++) { + targets[i] = baseAddress + in.readInt(); + } + + return new PackedSwitchPayloadDecodedInstruction( + this, opcodeUnit, firstKey, targets); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + PackedSwitchPayloadDecodedInstruction payload = + (PackedSwitchPayloadDecodedInstruction) insn; + int[] targets = payload.getTargets(); + int baseAddress = out.baseAddressForCursor(); + + out.write(payload.getOpcodeUnit()); + out.write(asUnsignedUnit(targets.length)); + out.writeInt(payload.getFirstKey()); + + for (int target : targets) { + out.writeInt(target - baseAddress); + } + } + }, + + FORMAT_SPARSE_SWITCH_PAYLOAD() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int baseAddress = in.baseAddressForCursor() - 1; // already read opcode + int size = in.read(); + int[] keys = new int[size]; + int[] targets = new int[size]; + + for (int i = 0; i < size; i++) { + keys[i] = in.readInt(); + } + + for (int i = 0; i < size; i++) { + targets[i] = baseAddress + in.readInt(); + } + + return new SparseSwitchPayloadDecodedInstruction( + this, opcodeUnit, keys, targets); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + SparseSwitchPayloadDecodedInstruction payload = + (SparseSwitchPayloadDecodedInstruction) insn; + int[] keys = payload.getKeys(); + int[] targets = payload.getTargets(); + int baseAddress = out.baseAddressForCursor(); + + out.write(payload.getOpcodeUnit()); + out.write(asUnsignedUnit(targets.length)); + + for (int key : keys) { + out.writeInt(key); + } + + for (int target : targets) { + out.writeInt(target - baseAddress); + } + } + }, + + FORMAT_FILL_ARRAY_DATA_PAYLOAD() { + @Override public DecodedInstruction decode(int opcodeUnit, + CodeInput in) throws EOFException { + int elementWidth = in.read(); + int size = in.readInt(); + + switch (elementWidth) { + case 1: { + byte[] array = new byte[size]; + boolean even = true; + for (int i = 0, value = 0; i < size; i++, even = !even) { + if (even) { + value = in.read(); + } + array[i] = (byte) (value & 0xff); + value >>= 8; + } + return new FillArrayDataPayloadDecodedInstruction( + this, opcodeUnit, array); + } + case 2: { + short[] array = new short[size]; + for (int i = 0; i < size; i++) { + array[i] = (short) in.read(); + } + return new FillArrayDataPayloadDecodedInstruction( + this, opcodeUnit, array); + } + case 4: { + int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = in.readInt(); + } + return new FillArrayDataPayloadDecodedInstruction( + this, opcodeUnit, array); + } + case 8: { + long[] array = new long[size]; + for (int i = 0; i < size; i++) { + array[i] = in.readLong(); + } + return new FillArrayDataPayloadDecodedInstruction( + this, opcodeUnit, array); + } + } + + throw new DexException("bogus element_width: " + + Hex.u2(elementWidth)); + } + + @Override public void encode(DecodedInstruction insn, CodeOutput out) { + FillArrayDataPayloadDecodedInstruction payload = + (FillArrayDataPayloadDecodedInstruction) insn; + short elementWidth = payload.getElementWidthUnit(); + Object data = payload.getData(); + + out.write(payload.getOpcodeUnit()); + out.write(elementWidth); + out.writeInt(payload.getSize()); + + switch (elementWidth) { + case 1: out.write((byte[]) data); break; + case 2: out.write((short[]) data); break; + case 4: out.write((int[]) data); break; + case 8: out.write((long[]) data); break; + default: { + throw new DexException("bogus element_width: " + + Hex.u2(elementWidth)); + } + } + } + }; + + /** + * Decodes an instruction specified by the given opcode unit, reading + * any required additional code units from the given input source. + */ + public abstract DecodedInstruction decode(int opcodeUnit, CodeInput in) + throws EOFException; + + /** + * Encodes the given instruction. + */ + public abstract void encode(DecodedInstruction insn, CodeOutput out); + + /** + * Helper method that decodes any of the register-list formats. + */ + private static DecodedInstruction decodeRegisterList( + InstructionCodec format, int opcodeUnit, CodeInput in) + throws EOFException { + int opcode = byte0(opcodeUnit); + int e = nibble2(opcodeUnit); + int registerCount = nibble3(opcodeUnit); + int index = in.read(); + int abcd = in.read(); + int a = nibble0(abcd); + int b = nibble1(abcd); + int c = nibble2(abcd); + int d = nibble3(abcd); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + + // TODO: Having to switch like this is less than ideal. + switch (registerCount) { + case 0: + return new ZeroRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L); + case 1: + return new OneRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a); + case 2: + return new TwoRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, b); + case 3: + return new ThreeRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, b, c); + case 4: + return new FourRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, b, c, d); + case 5: + return new FiveRegisterDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, b, c, d, e); + } + + throw new DexException("bogus registerCount: " + + Hex.uNibble(registerCount)); + } + + /** + * Helper method that encodes any of the register-list formats. + */ + private static void encodeRegisterList(DecodedInstruction insn, + CodeOutput out) { + out.write(codeUnit(insn.getOpcode(), + makeByte(insn.getE(), insn.getRegisterCount())), + insn.getIndexUnit(), + codeUnit(insn.getA(), insn.getB(), insn.getC(), insn.getD())); + } + + /** + * Helper method that decodes any of the three-unit register-range formats. + */ + private static DecodedInstruction decodeRegisterRange( + InstructionCodec format, int opcodeUnit, CodeInput in) + throws EOFException { + int opcode = byte0(opcodeUnit); + int registerCount = byte1(opcodeUnit); + int index = in.read(); + int a = in.read(); + IndexType indexType = OpcodeInfo.getIndexType(opcode); + return new RegisterRangeDecodedInstruction( + format, opcode, index, indexType, + 0, 0L, + a, registerCount); + } + + /** + * Helper method that encodes any of the three-unit register-range formats. + */ + private static void encodeRegisterRange(DecodedInstruction insn, + CodeOutput out) { + out.write(codeUnit(insn.getOpcode(), insn.getRegisterCount()), + insn.getIndexUnit(), + insn.getAUnit()); + } + + private static short codeUnit(int lowByte, int highByte) { + if ((lowByte & ~0xff) != 0) { + throw new IllegalArgumentException("bogus lowByte"); + } + + if ((highByte & ~0xff) != 0) { + throw new IllegalArgumentException("bogus highByte"); + } + + return (short) (lowByte | (highByte << 8)); + } + + private static short codeUnit(int nibble0, int nibble1, int nibble2, + int nibble3) { + if ((nibble0 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble0"); + } + + if ((nibble1 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble1"); + } + + if ((nibble2 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble2"); + } + + if ((nibble3 & ~0xf) != 0) { + throw new IllegalArgumentException("bogus nibble3"); + } + + return (short) (nibble0 | (nibble1 << 4) + | (nibble2 << 8) | (nibble3 << 12)); + } + + private static int makeByte(int lowNibble, int highNibble) { + if ((lowNibble & ~0xf) != 0) { + throw new IllegalArgumentException("bogus lowNibble"); + } + + if ((highNibble & ~0xf) != 0) { + throw new IllegalArgumentException("bogus highNibble"); + } + + return lowNibble | (highNibble << 4); + } + + private static short asUnsignedUnit(int value) { + if ((value & ~0xffff) != 0) { + throw new IllegalArgumentException("bogus unsigned code unit"); + } + + return (short) value; + } + + private static short unit0(int value) { + return (short) value; + } + + private static short unit1(int value) { + return (short) (value >> 16); + } + + private static short unit0(long value) { + return (short) value; + } + + private static short unit1(long value) { + return (short) (value >> 16); + } + + private static short unit2(long value) { + return (short) (value >> 32); + } + + private static short unit3(long value) { + return (short) (value >> 48); + } + + private static int byte0(int value) { + return value & 0xff; + } + + private static int byte1(int value) { + return (value >> 8) & 0xff; + } + + private static int byte2(int value) { + return (value >> 16) & 0xff; + } + + private static int byte3(int value) { + return value >>> 24; + } + + private static int nibble0(int value) { + return value & 0xf; + } + + private static int nibble1(int value) { + return (value >> 4) & 0xf; + } + + private static int nibble2(int value) { + return (value >> 8) & 0xf; + } + + private static int nibble3(int value) { + return (value >> 12) & 0xf; + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/OneRegisterDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/OneRegisterDecodedInstruction.java new file mode 100644 index 000000000..642ce40ca --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/OneRegisterDecodedInstruction.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import com.android.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has one register argument. + */ +public final class OneRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** + * Constructs an instance. + */ + public OneRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return 1; + } + + /** {@inheritDoc} */ + public int getA() { + return a; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + return new OneRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/PackedSwitchPayloadDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/PackedSwitchPayloadDecodedInstruction.java new file mode 100644 index 000000000..1e277e7ca --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/PackedSwitchPayloadDecodedInstruction.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +/** + * A decoded Dalvik instruction which contains the payload for + * a {@code packed-switch} instruction. + */ +public final class PackedSwitchPayloadDecodedInstruction + extends DecodedInstruction { + /** first key value */ + private final int firstKey; + + /** + * array of target addresses. These are absolute, not relative, + * addresses. + */ + private final int[] targets; + + /** + * Constructs an instance. + */ + public PackedSwitchPayloadDecodedInstruction(InstructionCodec format, + int opcode, int firstKey, int[] targets) { + super(format, opcode, 0, null, 0, 0L); + + this.firstKey = firstKey; + this.targets = targets; + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return 0; + } + + public int getFirstKey() { + return firstKey; + } + + public int[] getTargets() { + return targets; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + throw new UnsupportedOperationException("no index in instruction"); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/RegisterRangeDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/RegisterRangeDecodedInstruction.java new file mode 100644 index 000000000..98784776b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/RegisterRangeDecodedInstruction.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import com.android.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has register range arguments (an + * "A" start register and a register count). + */ +public final class RegisterRangeDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register count */ + private final int registerCount; + + /** + * Constructs an instance. + */ + public RegisterRangeDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int registerCount) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.registerCount = registerCount; + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return registerCount; + } + + /** {@inheritDoc} */ + public int getA() { + return a; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + return new RegisterRangeDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, registerCount); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/ShortArrayCodeInput.java b/dexlib/src/main/java/com/android/dx/io/instructions/ShortArrayCodeInput.java new file mode 100644 index 000000000..59cbc07ea --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/ShortArrayCodeInput.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import java.io.EOFException; + +/** + * Implementation of {@code CodeInput} that reads from a {@code short[]}. + */ +public final class ShortArrayCodeInput extends BaseCodeCursor + implements CodeInput { + /** source array to read from */ + private final short[] array; + + /** + * Constructs an instance. + */ + public ShortArrayCodeInput(short[] array) { + if (array == null) { + throw new NullPointerException("array == null"); + } + + this.array = array; + } + + /** {@inheritDoc} */ + public boolean hasMore() { + return cursor() < array.length; + } + + /** {@inheritDoc} */ + public int read() throws EOFException { + try { + int value = array[cursor()]; + advance(1); + return value & 0xffff; + } catch (ArrayIndexOutOfBoundsException ex) { + throw new EOFException(); + } + } + + /** {@inheritDoc} */ + public int readInt() throws EOFException { + int short0 = read(); + int short1 = read(); + + return short0 | (short1 << 16); + } + + /** {@inheritDoc} */ + public long readLong() throws EOFException { + long short0 = read(); + long short1 = read(); + long short2 = read(); + long short3 = read(); + + return short0 | (short1 << 16) | (short2 << 32) | (short3 << 48); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/ShortArrayCodeOutput.java b/dexlib/src/main/java/com/android/dx/io/instructions/ShortArrayCodeOutput.java new file mode 100644 index 000000000..053af07bd --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/ShortArrayCodeOutput.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +/** + * Implementation of {@code CodeOutput} that writes to a {@code short[]}. + */ +public final class ShortArrayCodeOutput extends BaseCodeCursor + implements CodeOutput { + /** array to write to */ + private final short[] array; + + /** + * Constructs an instance. + * + * @param maxSize the maximum number of code units that will be written + */ + public ShortArrayCodeOutput(int maxSize) { + if (maxSize < 0) { + throw new IllegalArgumentException("maxSize < 0"); + } + + this.array = new short[maxSize]; + } + + /** + * Gets the array. The returned array contains exactly the data + * written (e.g. no leftover space at the end). + */ + public short[] getArray() { + int cursor = cursor(); + + if (cursor == array.length) { + return array; + } + + short[] result = new short[cursor]; + System.arraycopy(array, 0, result, 0, cursor); + return result; + } + + /** {@inheritDoc} */ + public void write(short codeUnit) { + array[cursor()] = codeUnit; + advance(1); + } + + /** {@inheritDoc} */ + public void write(short u0, short u1) { + write(u0); + write(u1); + } + + /** {@inheritDoc} */ + public void write(short u0, short u1, short u2) { + write(u0); + write(u1); + write(u2); + } + + /** {@inheritDoc} */ + public void write(short u0, short u1, short u2, short u3) { + write(u0); + write(u1); + write(u2); + write(u3); + } + + /** {@inheritDoc} */ + public void write(short u0, short u1, short u2, short u3, short u4) { + write(u0); + write(u1); + write(u2); + write(u3); + write(u4); + } + + /** {@inheritDoc} */ + public void writeInt(int value) { + write((short) value); + write((short) (value >> 16)); + } + + /** {@inheritDoc} */ + public void writeLong(long value) { + write((short) value); + write((short) (value >> 16)); + write((short) (value >> 32)); + write((short) (value >> 48)); + } + + /** {@inheritDoc} */ + public void write(byte[] data) { + int value = 0; + boolean even = true; + for (byte b : data) { + if (even) { + value = b & 0xff; + even = false; + } else { + value |= b << 8; + write((short) value); + even = true; + } + } + + if (!even) { + write((short) value); + } + } + + /** {@inheritDoc} */ + public void write(short[] data) { + for (short unit : data) { + write(unit); + } + } + + /** {@inheritDoc} */ + public void write(int[] data) { + for (int i : data) { + writeInt(i); + } + } + + /** {@inheritDoc} */ + public void write(long[] data) { + for (long l : data) { + writeLong(l); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/SparseSwitchPayloadDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/SparseSwitchPayloadDecodedInstruction.java new file mode 100644 index 000000000..8d6431354 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/SparseSwitchPayloadDecodedInstruction.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +/** + * A decoded Dalvik instruction which contains the payload for + * a {@code packed-switch} instruction. + */ +public final class SparseSwitchPayloadDecodedInstruction + extends DecodedInstruction { + /** array of key values */ + private final int[] keys; + + /** + * array of target addresses. These are absolute, not relative, + * addresses. + */ + private final int[] targets; + + /** + * Constructs an instance. + */ + public SparseSwitchPayloadDecodedInstruction(InstructionCodec format, + int opcode, int[] keys, int[] targets) { + super(format, opcode, 0, null, 0, 0L); + + if (keys.length != targets.length) { + throw new IllegalArgumentException("keys/targets length mismatch"); + } + + this.keys = keys; + this.targets = targets; + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return 0; + } + + public int[] getKeys() { + return keys; + } + + public int[] getTargets() { + return targets; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + throw new UnsupportedOperationException("no index in instruction"); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/ThreeRegisterDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/ThreeRegisterDecodedInstruction.java new file mode 100644 index 000000000..f91421738 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/ThreeRegisterDecodedInstruction.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import com.android.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has three register arguments. + */ +public final class ThreeRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register argument "B" */ + private final int b; + + /** register argument "C" */ + private final int c; + + /** + * Constructs an instance. + */ + public ThreeRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int b, int c) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.b = b; + this.c = c; + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return 3; + } + + /** {@inheritDoc} */ + public int getA() { + return a; + } + + /** {@inheritDoc} */ + public int getB() { + return b; + } + + /** {@inheritDoc} */ + public int getC() { + return c; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + return new ThreeRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, b, c); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/TwoRegisterDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/TwoRegisterDecodedInstruction.java new file mode 100644 index 000000000..88b1a48cf --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/TwoRegisterDecodedInstruction.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import com.android.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has two register arguments. + */ +public final class TwoRegisterDecodedInstruction extends DecodedInstruction { + /** register argument "A" */ + private final int a; + + /** register argument "B" */ + private final int b; + + /** + * Constructs an instance. + */ + public TwoRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal, + int a, int b) { + super(format, opcode, index, indexType, target, literal); + + this.a = a; + this.b = b; + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return 2; + } + + /** {@inheritDoc} */ + public int getA() { + return a; + } + + /** {@inheritDoc} */ + public int getB() { + return b; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + return new TwoRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral(), a, b); + } +} diff --git a/dexlib/src/main/java/com/android/dx/io/instructions/ZeroRegisterDecodedInstruction.java b/dexlib/src/main/java/com/android/dx/io/instructions/ZeroRegisterDecodedInstruction.java new file mode 100644 index 000000000..47fc41494 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/io/instructions/ZeroRegisterDecodedInstruction.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.io.instructions; + +import com.android.dx.io.IndexType; + +/** + * A decoded Dalvik instruction which has no register arguments. + */ +public final class ZeroRegisterDecodedInstruction extends DecodedInstruction { + /** + * Constructs an instance. + */ + public ZeroRegisterDecodedInstruction(InstructionCodec format, int opcode, + int index, IndexType indexType, int target, long literal) { + super(format, opcode, index, indexType, target, literal); + } + + /** {@inheritDoc} */ + public int getRegisterCount() { + return 0; + } + + /** {@inheritDoc} */ + public DecodedInstruction withIndex(int newIndex) { + return new ZeroRegisterDecodedInstruction( + getFormat(), getOpcode(), newIndex, getIndexType(), + getTarget(), getLiteral()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/merge/CollisionPolicy.java b/dexlib/src/main/java/com/android/dx/merge/CollisionPolicy.java new file mode 100644 index 000000000..95e9835d5 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/merge/CollisionPolicy.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.merge; + +/** + * What to do when two dex files define the same class. + */ +public enum CollisionPolicy { + + /** + * Keep the class def from the first dex file and discard the def from the + * second dex file. This policy is appropriate for incremental builds. + */ + KEEP_FIRST, + + /** + * Forbid collisions. This policy is appropriate for merging libraries. + */ + FAIL +} diff --git a/dexlib/src/main/java/com/android/dx/merge/DexMerger.java b/dexlib/src/main/java/com/android/dx/merge/DexMerger.java new file mode 100644 index 000000000..81ad2ddb7 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/merge/DexMerger.java @@ -0,0 +1,1147 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.merge; + +import com.android.dex.Annotation; +import com.android.dex.ClassData; +import com.android.dex.ClassDef; +import com.android.dex.Code; +import com.android.dex.Dex; +import com.android.dex.DexException; +import com.android.dex.DexIndexOverflowException; +import com.android.dex.FieldId; +import com.android.dex.MethodId; +import com.android.dex.ProtoId; +import com.android.dex.SizeOf; +import com.android.dex.TableOfContents; +import com.android.dex.TypeList; +import com.android.dx.command.dexer.DxContext; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * Combine two dex files into one. + */ +public final class DexMerger { + private final Dex[] dexes; + private final IndexMap[] indexMaps; + + private final CollisionPolicy collisionPolicy; + private final DxContext context; + private final WriterSizes writerSizes; + + private final Dex dexOut; + + private final Dex.Section headerOut; + + /** All IDs and definitions sections */ + private final Dex.Section idsDefsOut; + + private final Dex.Section mapListOut; + + private final Dex.Section typeListOut; + + private final Dex.Section classDataOut; + + private final Dex.Section codeOut; + + private final Dex.Section stringDataOut; + + private final Dex.Section debugInfoOut; + + private final Dex.Section encodedArrayOut; + + /** annotations directory on a type */ + private final Dex.Section annotationsDirectoryOut; + + /** sets of annotations on a member, parameter or type */ + private final Dex.Section annotationSetOut; + + /** parameter lists */ + private final Dex.Section annotationSetRefListOut; + + /** individual annotations, each containing zero or more fields */ + private final Dex.Section annotationOut; + + private final TableOfContents contentsOut; + + private final InstructionTransformer instructionTransformer; + + /** minimum number of wasted bytes before it's worthwhile to compact the result */ + private int compactWasteThreshold = 1024 * 1024; // 1MiB + + public DexMerger(Dex[] dexes, CollisionPolicy collisionPolicy, DxContext context) + throws IOException { + this(dexes, collisionPolicy, context, new WriterSizes(dexes)); + } + + private DexMerger(Dex[] dexes, CollisionPolicy collisionPolicy, DxContext context, + WriterSizes writerSizes) throws IOException { + this.dexes = dexes; + this.collisionPolicy = collisionPolicy; + this.context = context; + this.writerSizes = writerSizes; + + dexOut = new Dex(writerSizes.size()); + + indexMaps = new IndexMap[dexes.length]; + for (int i = 0; i < dexes.length; i++) { + indexMaps[i] = new IndexMap(dexOut, dexes[i].getTableOfContents()); + } + instructionTransformer = new InstructionTransformer(); + + headerOut = dexOut.appendSection(writerSizes.header, "header"); + idsDefsOut = dexOut.appendSection(writerSizes.idsDefs, "ids defs"); + + contentsOut = dexOut.getTableOfContents(); + contentsOut.dataOff = dexOut.getNextSectionStart(); + + contentsOut.mapList.off = dexOut.getNextSectionStart(); + contentsOut.mapList.size = 1; + mapListOut = dexOut.appendSection(writerSizes.mapList, "map list"); + + contentsOut.typeLists.off = dexOut.getNextSectionStart(); + typeListOut = dexOut.appendSection(writerSizes.typeList, "type list"); + + contentsOut.annotationSetRefLists.off = dexOut.getNextSectionStart(); + annotationSetRefListOut = dexOut.appendSection( + writerSizes.annotationsSetRefList, "annotation set ref list"); + + contentsOut.annotationSets.off = dexOut.getNextSectionStart(); + annotationSetOut = dexOut.appendSection(writerSizes.annotationsSet, "annotation sets"); + + contentsOut.classDatas.off = dexOut.getNextSectionStart(); + classDataOut = dexOut.appendSection(writerSizes.classData, "class data"); + + contentsOut.codes.off = dexOut.getNextSectionStart(); + codeOut = dexOut.appendSection(writerSizes.code, "code"); + + contentsOut.stringDatas.off = dexOut.getNextSectionStart(); + stringDataOut = dexOut.appendSection(writerSizes.stringData, "string data"); + + contentsOut.debugInfos.off = dexOut.getNextSectionStart(); + debugInfoOut = dexOut.appendSection(writerSizes.debugInfo, "debug info"); + + contentsOut.annotations.off = dexOut.getNextSectionStart(); + annotationOut = dexOut.appendSection(writerSizes.annotation, "annotation"); + + contentsOut.encodedArrays.off = dexOut.getNextSectionStart(); + encodedArrayOut = dexOut.appendSection(writerSizes.encodedArray, "encoded array"); + + contentsOut.annotationsDirectories.off = dexOut.getNextSectionStart(); + annotationsDirectoryOut = dexOut.appendSection( + writerSizes.annotationsDirectory, "annotations directory"); + + contentsOut.dataSize = dexOut.getNextSectionStart() - contentsOut.dataOff; + } + + public void setCompactWasteThreshold(int compactWasteThreshold) { + this.compactWasteThreshold = compactWasteThreshold; + } + + private Dex mergeDexes() throws IOException { + mergeStringIds(); + mergeTypeIds(); + mergeTypeLists(); + mergeProtoIds(); + mergeFieldIds(); + mergeMethodIds(); + mergeAnnotations(); + unionAnnotationSetsAndDirectories(); + mergeClassDefs(); + + // computeSizesFromOffsets expects sections sorted by offset, so make it so + Arrays.sort(contentsOut.sections); + + // write the header + contentsOut.header.off = 0; + contentsOut.header.size = 1; + contentsOut.fileSize = dexOut.getLength(); + contentsOut.computeSizesFromOffsets(); + contentsOut.writeHeader(headerOut, mergeApiLevels()); + contentsOut.writeMap(mapListOut); + + // generate and write the hashes + dexOut.writeHashes(); + + return dexOut; + } + + public Dex merge() throws IOException { + if (dexes.length == 1) { + return dexes[0]; + } else if (dexes.length == 0) { + return null; + } + + long start = System.nanoTime(); + Dex result = mergeDexes(); + + /* + * We use pessimistic sizes when merging dex files. If those sizes + * result in too many bytes wasted, compact the result. To compact, + * simply merge the result with itself. + */ + WriterSizes compactedSizes = new WriterSizes(this); + int wastedByteCount = writerSizes.size() - compactedSizes.size(); + if (wastedByteCount > + compactWasteThreshold) { + DexMerger compacter = new DexMerger( + new Dex[] {dexOut, new Dex(0)}, CollisionPolicy.FAIL, context, compactedSizes); + result = compacter.mergeDexes(); + context.out.printf("Result compacted from %.1fKiB to %.1fKiB to save %.1fKiB%n", + dexOut.getLength() / 1024f, + result.getLength() / 1024f, + wastedByteCount / 1024f); + } + + long elapsed = System.nanoTime() - start; + for (int i = 0; i < dexes.length; i++) { + context.out.printf("Merged dex #%d (%d defs/%.1fKiB)%n", + i + 1, + dexes[i].getTableOfContents().classDefs.size, + dexes[i].getLength() / 1024f); + } + context.out.printf("Result is %d defs/%.1fKiB. Took %.1fs%n", + result.getTableOfContents().classDefs.size, + result.getLength() / 1024f, + elapsed / 1000000000f); + + return result; + } + + /** + * Reads an IDs section of two dex files and writes an IDs section of a + * merged dex file. Populates maps from old to new indices in the process. + */ + abstract class IdMerger> { + private final Dex.Section out; + + protected IdMerger(Dex.Section out) { + this.out = out; + } + + /** + * Merges already-sorted sections, reading one value from each dex into memory + * at a time. + */ + public final void mergeSorted() { + TableOfContents.Section[] sections = new TableOfContents.Section[dexes.length]; + Dex.Section[] dexSections = new Dex.Section[dexes.length]; + int[] offsets = new int[dexes.length]; + int[] indexes = new int[dexes.length]; + + // values contains one value from each dex, sorted for fast retrieval of + // the smallest value. The list associated with a value has the indexes + // of the dexes that had that value. + TreeMap> values = new TreeMap>(); + + for (int i = 0; i < dexes.length; i++) { + sections[i] = getSection(dexes[i].getTableOfContents()); + dexSections[i] = sections[i].exists() ? dexes[i].open(sections[i].off) : null; + // Fill in values with the first value of each dex. + offsets[i] = readIntoMap( + dexSections[i], sections[i], indexMaps[i], indexes[i], values, i); + } + if (values.isEmpty()) { + getSection(contentsOut).off = 0; + getSection(contentsOut).size = 0; + return; + } + getSection(contentsOut).off = out.getPosition(); + + int outCount = 0; + while (!values.isEmpty()) { + Map.Entry> first = values.pollFirstEntry(); + for (Integer dex : first.getValue()) { + updateIndex(offsets[dex], indexMaps[dex], indexes[dex]++, outCount); + // Fetch the next value of the dexes we just polled out + offsets[dex] = readIntoMap(dexSections[dex], sections[dex], + indexMaps[dex], indexes[dex], values, dex); + } + write(first.getKey()); + outCount++; + } + + getSection(contentsOut).size = outCount; + } + + private int readIntoMap(Dex.Section in, TableOfContents.Section section, IndexMap indexMap, + int index, TreeMap> values, int dex) { + int offset = in != null ? in.getPosition() : -1; + if (index < section.size) { + T v = read(in, indexMap, index); + List l = values.get(v); + if (l == null) { + l = new ArrayList(); + values.put(v, l); + } + l.add(new Integer(dex)); + } + return offset; + } + + /** + * Merges unsorted sections by reading them completely into memory and + * sorting in memory. + */ + public final void mergeUnsorted() { + getSection(contentsOut).off = out.getPosition(); + + List all = new ArrayList(); + for (int i = 0; i < dexes.length; i++) { + all.addAll(readUnsortedValues(dexes[i], indexMaps[i])); + } + if (all.isEmpty()) { + getSection(contentsOut).off = 0; + getSection(contentsOut).size = 0; + return; + } + Collections.sort(all); + + int outCount = 0; + for (int i = 0; i < all.size(); ) { + UnsortedValue e1 = all.get(i++); + updateIndex(e1.offset, e1.indexMap, e1.index, outCount - 1); + + while (i < all.size() && e1.compareTo(all.get(i)) == 0) { + UnsortedValue e2 = all.get(i++); + updateIndex(e2.offset, e2.indexMap, e2.index, outCount - 1); + } + + write(e1.value); + outCount++; + } + + getSection(contentsOut).size = outCount; + } + + private List readUnsortedValues(Dex source, IndexMap indexMap) { + TableOfContents.Section section = getSection(source.getTableOfContents()); + if (!section.exists()) { + return Collections.emptyList(); + } + + List result = new ArrayList(); + Dex.Section in = source.open(section.off); + for (int i = 0; i < section.size; i++) { + int offset = in.getPosition(); + T value = read(in, indexMap, 0); + result.add(new UnsortedValue(source, indexMap, value, i, offset)); + } + return result; + } + + abstract TableOfContents.Section getSection(TableOfContents tableOfContents); + abstract T read(Dex.Section in, IndexMap indexMap, int index); + abstract void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex); + abstract void write(T value); + + class UnsortedValue implements Comparable { + final Dex source; + final IndexMap indexMap; + final T value; + final int index; + final int offset; + + UnsortedValue(Dex source, IndexMap indexMap, T value, int index, int offset) { + this.source = source; + this.indexMap = indexMap; + this.value = value; + this.index = index; + this.offset = offset; + } + + @Override + public int compareTo(UnsortedValue unsortedValue) { + return value.compareTo(unsortedValue.value); + } + } + } + + private int mergeApiLevels() { + int maxApi = -1; + for (int i = 0; i < dexes.length; i++) { + int dexMinApi = dexes[i].getTableOfContents().apiLevel; + if (maxApi < dexMinApi) { + maxApi = dexMinApi; + } + } + return maxApi; + } + + private void mergeStringIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.stringIds; + } + + @Override String read(Dex.Section in, IndexMap indexMap, int index) { + return in.readString(); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + indexMap.stringIds[oldIndex] = newIndex; + } + + @Override void write(String value) { + contentsOut.stringDatas.size++; + idsDefsOut.writeInt(stringDataOut.getPosition()); + stringDataOut.writeStringData(value); + } + }.mergeSorted(); + } + + private void mergeTypeIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.typeIds; + } + + @Override Integer read(Dex.Section in, IndexMap indexMap, int index) { + int stringIndex = in.readInt(); + return indexMap.adjustString(stringIndex); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + if (newIndex < 0 || newIndex > 0xffff) { + throw new DexIndexOverflowException("type ID not in [0, 0xffff]: " + newIndex); + } + indexMap.typeIds[oldIndex] = (short) newIndex; + } + + @Override void write(Integer value) { + idsDefsOut.writeInt(value); + } + }.mergeSorted(); + } + + private void mergeTypeLists() { + new IdMerger(typeListOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.typeLists; + } + + @Override TypeList read(Dex.Section in, IndexMap indexMap, int index) { + return indexMap.adjustTypeList(in.readTypeList()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + indexMap.putTypeListOffset(offset, typeListOut.getPosition()); + } + + @Override void write(TypeList value) { + typeListOut.writeTypeList(value); + } + }.mergeUnsorted(); + } + + private void mergeProtoIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.protoIds; + } + + @Override ProtoId read(Dex.Section in, IndexMap indexMap, int index) { + return indexMap.adjust(in.readProtoId()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + if (newIndex < 0 || newIndex > 0xffff) { + throw new DexIndexOverflowException("proto ID not in [0, 0xffff]: " + newIndex); + } + indexMap.protoIds[oldIndex] = (short) newIndex; + } + + @Override void write(ProtoId value) { + value.writeTo(idsDefsOut); + } + }.mergeSorted(); + } + + private void mergeFieldIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.fieldIds; + } + + @Override FieldId read(Dex.Section in, IndexMap indexMap, int index) { + return indexMap.adjust(in.readFieldId()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + if (newIndex < 0 || newIndex > 0xffff) { + throw new DexIndexOverflowException("field ID not in [0, 0xffff]: " + newIndex); + } + indexMap.fieldIds[oldIndex] = (short) newIndex; + } + + @Override void write(FieldId value) { + value.writeTo(idsDefsOut); + } + }.mergeSorted(); + } + + private void mergeMethodIds() { + new IdMerger(idsDefsOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.methodIds; + } + + @Override MethodId read(Dex.Section in, IndexMap indexMap, int index) { + return indexMap.adjust(in.readMethodId()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + if (newIndex < 0 || newIndex > 0xffff) { + throw new DexIndexOverflowException( + "method ID not in [0, 0xffff]: " + newIndex); + } + indexMap.methodIds[oldIndex] = (short) newIndex; + } + + @Override void write(MethodId methodId) { + methodId.writeTo(idsDefsOut); + } + }.mergeSorted(); + } + + private void mergeAnnotations() { + new IdMerger(annotationOut) { + @Override TableOfContents.Section getSection(TableOfContents tableOfContents) { + return tableOfContents.annotations; + } + + @Override Annotation read(Dex.Section in, IndexMap indexMap, int index) { + return indexMap.adjust(in.readAnnotation()); + } + + @Override void updateIndex(int offset, IndexMap indexMap, int oldIndex, int newIndex) { + indexMap.putAnnotationOffset(offset, annotationOut.getPosition()); + } + + @Override void write(Annotation value) { + value.writeTo(annotationOut); + } + }.mergeUnsorted(); + } + + private void mergeClassDefs() { + SortableType[] types = getSortedTypes(); + contentsOut.classDefs.off = idsDefsOut.getPosition(); + contentsOut.classDefs.size = types.length; + + for (SortableType type : types) { + Dex in = type.getDex(); + transformClassDef(in, type.getClassDef(), type.getIndexMap()); + } + } + + /** + * Returns the union of classes from both files, sorted in order such that + * a class is always preceded by its supertype and implemented interfaces. + */ + private SortableType[] getSortedTypes() { + // size is pessimistic; doesn't include arrays + SortableType[] sortableTypes = new SortableType[contentsOut.typeIds.size]; + for (int i = 0; i < dexes.length; i++) { + readSortableTypes(sortableTypes, dexes[i], indexMaps[i]); + } + + /* + * Populate the depths of each sortable type. This makes D iterations + * through all N types, where 'D' is the depth of the deepest type. For + * example, the deepest class in libcore is Xalan's KeyIterator, which + * is 11 types deep. + */ + while (true) { + boolean allDone = true; + for (SortableType sortableType : sortableTypes) { + if (sortableType != null && !sortableType.isDepthAssigned()) { + allDone &= sortableType.tryAssignDepth(sortableTypes); + } + } + if (allDone) { + break; + } + } + + // Now that all types have depth information, the result can be sorted + Arrays.sort(sortableTypes, SortableType.NULLS_LAST_ORDER); + + // Strip nulls from the end + int firstNull = Arrays.asList(sortableTypes).indexOf(null); + return firstNull != -1 + ? Arrays.copyOfRange(sortableTypes, 0, firstNull) + : sortableTypes; + } + + /** + * Reads just enough data on each class so that we can sort it and then find + * it later. + */ + private void readSortableTypes(SortableType[] sortableTypes, Dex buffer, + IndexMap indexMap) { + for (ClassDef classDef : buffer.classDefs()) { + SortableType sortableType = indexMap.adjust( + new SortableType(buffer, indexMap, classDef)); + int t = sortableType.getTypeIndex(); + if (sortableTypes[t] == null) { + sortableTypes[t] = sortableType; + } else if (collisionPolicy != CollisionPolicy.KEEP_FIRST) { + throw new DexException("Multiple dex files define " + + buffer.typeNames().get(classDef.getTypeIndex())); + } + } + } + + /** + * Copy annotation sets from each input to the output. + * + * TODO: this may write multiple copies of the same annotation set. + * We should shrink the output by merging rather than unioning + */ + private void unionAnnotationSetsAndDirectories() { + for (int i = 0; i < dexes.length; i++) { + transformAnnotationSets(dexes[i], indexMaps[i]); + } + for (int i = 0; i < dexes.length; i++) { + transformAnnotationSetRefLists(dexes[i], indexMaps[i]); + } + for (int i = 0; i < dexes.length; i++) { + transformAnnotationDirectories(dexes[i], indexMaps[i]); + } + for (int i = 0; i < dexes.length; i++) { + transformStaticValues(dexes[i], indexMaps[i]); + } + } + + private void transformAnnotationSets(Dex in, IndexMap indexMap) { + TableOfContents.Section section = in.getTableOfContents().annotationSets; + if (section.exists()) { + Dex.Section setIn = in.open(section.off); + for (int i = 0; i < section.size; i++) { + transformAnnotationSet(indexMap, setIn); + } + } + } + + private void transformAnnotationSetRefLists(Dex in, IndexMap indexMap) { + TableOfContents.Section section = in.getTableOfContents().annotationSetRefLists; + if (section.exists()) { + Dex.Section setIn = in.open(section.off); + for (int i = 0; i < section.size; i++) { + transformAnnotationSetRefList(indexMap, setIn); + } + } + } + + private void transformAnnotationDirectories(Dex in, IndexMap indexMap) { + TableOfContents.Section section = in.getTableOfContents().annotationsDirectories; + if (section.exists()) { + Dex.Section directoryIn = in.open(section.off); + for (int i = 0; i < section.size; i++) { + transformAnnotationDirectory(directoryIn, indexMap); + } + } + } + + private void transformStaticValues(Dex in, IndexMap indexMap) { + TableOfContents.Section section = in.getTableOfContents().encodedArrays; + if (section.exists()) { + Dex.Section staticValuesIn = in.open(section.off); + for (int i = 0; i < section.size; i++) { + transformStaticValues(staticValuesIn, indexMap); + } + } + } + + /** + * Reads a class_def_item beginning at {@code in} and writes the index and + * data. + */ + private void transformClassDef(Dex in, ClassDef classDef, IndexMap indexMap) { + idsDefsOut.assertFourByteAligned(); + idsDefsOut.writeInt(classDef.getTypeIndex()); + idsDefsOut.writeInt(classDef.getAccessFlags()); + idsDefsOut.writeInt(classDef.getSupertypeIndex()); + idsDefsOut.writeInt(classDef.getInterfacesOffset()); + + int sourceFileIndex = indexMap.adjustString(classDef.getSourceFileIndex()); + idsDefsOut.writeInt(sourceFileIndex); + + int annotationsOff = classDef.getAnnotationsOffset(); + idsDefsOut.writeInt(indexMap.adjustAnnotationDirectory(annotationsOff)); + + int classDataOff = classDef.getClassDataOffset(); + if (classDataOff == 0) { + idsDefsOut.writeInt(0); + } else { + idsDefsOut.writeInt(classDataOut.getPosition()); + ClassData classData = in.readClassData(classDef); + transformClassData(in, classData, indexMap); + } + + int staticValuesOff = classDef.getStaticValuesOffset(); + idsDefsOut.writeInt(indexMap.adjustStaticValues(staticValuesOff)); + } + + /** + * Transform all annotations on a class. + */ + private void transformAnnotationDirectory( + Dex.Section directoryIn, IndexMap indexMap) { + contentsOut.annotationsDirectories.size++; + annotationsDirectoryOut.assertFourByteAligned(); + indexMap.putAnnotationDirectoryOffset( + directoryIn.getPosition(), annotationsDirectoryOut.getPosition()); + + int classAnnotationsOffset = indexMap.adjustAnnotationSet(directoryIn.readInt()); + annotationsDirectoryOut.writeInt(classAnnotationsOffset); + + int fieldsSize = directoryIn.readInt(); + annotationsDirectoryOut.writeInt(fieldsSize); + + int methodsSize = directoryIn.readInt(); + annotationsDirectoryOut.writeInt(methodsSize); + + int parameterListSize = directoryIn.readInt(); + annotationsDirectoryOut.writeInt(parameterListSize); + + for (int i = 0; i < fieldsSize; i++) { + // field index + annotationsDirectoryOut.writeInt(indexMap.adjustField(directoryIn.readInt())); + + // annotations offset + annotationsDirectoryOut.writeInt(indexMap.adjustAnnotationSet(directoryIn.readInt())); + } + + for (int i = 0; i < methodsSize; i++) { + // method index + annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); + + // annotation set offset + annotationsDirectoryOut.writeInt( + indexMap.adjustAnnotationSet(directoryIn.readInt())); + } + + for (int i = 0; i < parameterListSize; i++) { + // method index + annotationsDirectoryOut.writeInt(indexMap.adjustMethod(directoryIn.readInt())); + + // annotations offset + annotationsDirectoryOut.writeInt( + indexMap.adjustAnnotationSetRefList(directoryIn.readInt())); + } + } + + /** + * Transform all annotations on a single type, member or parameter. + */ + private void transformAnnotationSet(IndexMap indexMap, Dex.Section setIn) { + contentsOut.annotationSets.size++; + annotationSetOut.assertFourByteAligned(); + indexMap.putAnnotationSetOffset(setIn.getPosition(), annotationSetOut.getPosition()); + + int size = setIn.readInt(); + annotationSetOut.writeInt(size); + + for (int j = 0; j < size; j++) { + annotationSetOut.writeInt(indexMap.adjustAnnotation(setIn.readInt())); + } + } + + /** + * Transform all annotation set ref lists. + */ + private void transformAnnotationSetRefList(IndexMap indexMap, Dex.Section refListIn) { + contentsOut.annotationSetRefLists.size++; + annotationSetRefListOut.assertFourByteAligned(); + indexMap.putAnnotationSetRefListOffset( + refListIn.getPosition(), annotationSetRefListOut.getPosition()); + + int parameterCount = refListIn.readInt(); + annotationSetRefListOut.writeInt(parameterCount); + for (int p = 0; p < parameterCount; p++) { + annotationSetRefListOut.writeInt(indexMap.adjustAnnotationSet(refListIn.readInt())); + } + } + + private void transformClassData(Dex in, ClassData classData, IndexMap indexMap) { + contentsOut.classDatas.size++; + + ClassData.Field[] staticFields = classData.getStaticFields(); + ClassData.Field[] instanceFields = classData.getInstanceFields(); + ClassData.Method[] directMethods = classData.getDirectMethods(); + ClassData.Method[] virtualMethods = classData.getVirtualMethods(); + + classDataOut.writeUleb128(staticFields.length); + classDataOut.writeUleb128(instanceFields.length); + classDataOut.writeUleb128(directMethods.length); + classDataOut.writeUleb128(virtualMethods.length); + + transformFields(indexMap, staticFields); + transformFields(indexMap, instanceFields); + transformMethods(in, indexMap, directMethods); + transformMethods(in, indexMap, virtualMethods); + } + + private void transformFields(IndexMap indexMap, ClassData.Field[] fields) { + int lastOutFieldIndex = 0; + for (ClassData.Field field : fields) { + int outFieldIndex = indexMap.adjustField(field.getFieldIndex()); + classDataOut.writeUleb128(outFieldIndex - lastOutFieldIndex); + lastOutFieldIndex = outFieldIndex; + classDataOut.writeUleb128(field.getAccessFlags()); + } + } + + private void transformMethods(Dex in, IndexMap indexMap, ClassData.Method[] methods) { + int lastOutMethodIndex = 0; + for (ClassData.Method method : methods) { + int outMethodIndex = indexMap.adjustMethod(method.getMethodIndex()); + classDataOut.writeUleb128(outMethodIndex - lastOutMethodIndex); + lastOutMethodIndex = outMethodIndex; + + classDataOut.writeUleb128(method.getAccessFlags()); + + if (method.getCodeOffset() == 0) { + classDataOut.writeUleb128(0); + } else { + codeOut.alignToFourBytesWithZeroFill(); + classDataOut.writeUleb128(codeOut.getPosition()); + transformCode(in, in.readCode(method), indexMap); + } + } + } + + private void transformCode(Dex in, Code code, IndexMap indexMap) { + contentsOut.codes.size++; + codeOut.assertFourByteAligned(); + + codeOut.writeUnsignedShort(code.getRegistersSize()); + codeOut.writeUnsignedShort(code.getInsSize()); + codeOut.writeUnsignedShort(code.getOutsSize()); + + Code.Try[] tries = code.getTries(); + Code.CatchHandler[] catchHandlers = code.getCatchHandlers(); + codeOut.writeUnsignedShort(tries.length); + + int debugInfoOffset = code.getDebugInfoOffset(); + if (debugInfoOffset != 0) { + codeOut.writeInt(debugInfoOut.getPosition()); + transformDebugInfoItem(in.open(debugInfoOffset), indexMap); + } else { + codeOut.writeInt(0); + } + + short[] instructions = code.getInstructions(); + short[] newInstructions = instructionTransformer.transform(indexMap, instructions); + codeOut.writeInt(newInstructions.length); + codeOut.write(newInstructions); + + if (tries.length > 0) { + if (newInstructions.length % 2 == 1) { + codeOut.writeShort((short) 0); // padding + } + + /* + * We can't write the tries until we've written the catch handlers. + * Unfortunately they're in the opposite order in the dex file so we + * need to transform them out-of-order. + */ + Dex.Section triesSection = dexOut.open(codeOut.getPosition()); + codeOut.skip(tries.length * SizeOf.TRY_ITEM); + int[] offsets = transformCatchHandlers(indexMap, catchHandlers); + transformTries(triesSection, tries, offsets); + } + } + + /** + * Writes the catch handlers to {@code codeOut} and returns their indices. + */ + private int[] transformCatchHandlers(IndexMap indexMap, Code.CatchHandler[] catchHandlers) { + int baseOffset = codeOut.getPosition(); + codeOut.writeUleb128(catchHandlers.length); + int[] offsets = new int[catchHandlers.length]; + for (int i = 0; i < catchHandlers.length; i++) { + offsets[i] = codeOut.getPosition() - baseOffset; + transformEncodedCatchHandler(catchHandlers[i], indexMap); + } + return offsets; + } + + private void transformTries(Dex.Section out, Code.Try[] tries, + int[] catchHandlerOffsets) { + for (Code.Try tryItem : tries) { + out.writeInt(tryItem.getStartAddress()); + out.writeUnsignedShort(tryItem.getInstructionCount()); + out.writeUnsignedShort(catchHandlerOffsets[tryItem.getCatchHandlerIndex()]); + } + } + + private static final byte DBG_END_SEQUENCE = 0x00; + private static final byte DBG_ADVANCE_PC = 0x01; + private static final byte DBG_ADVANCE_LINE = 0x02; + private static final byte DBG_START_LOCAL = 0x03; + private static final byte DBG_START_LOCAL_EXTENDED = 0x04; + private static final byte DBG_END_LOCAL = 0x05; + private static final byte DBG_RESTART_LOCAL = 0x06; + private static final byte DBG_SET_PROLOGUE_END = 0x07; + private static final byte DBG_SET_EPILOGUE_BEGIN = 0x08; + private static final byte DBG_SET_FILE = 0x09; + + private void transformDebugInfoItem(Dex.Section in, IndexMap indexMap) { + contentsOut.debugInfos.size++; + int lineStart = in.readUleb128(); + debugInfoOut.writeUleb128(lineStart); + + int parametersSize = in.readUleb128(); + debugInfoOut.writeUleb128(parametersSize); + + for (int p = 0; p < parametersSize; p++) { + int parameterName = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustString(parameterName)); + } + + int addrDiff; // uleb128 address delta. + int lineDiff; // sleb128 line delta. + int registerNum; // uleb128 register number. + int nameIndex; // uleb128p1 string index. Needs indexMap adjustment. + int typeIndex; // uleb128p1 type index. Needs indexMap adjustment. + int sigIndex; // uleb128p1 string index. Needs indexMap adjustment. + + while (true) { + int opcode = in.readByte(); + debugInfoOut.writeByte(opcode); + + switch (opcode) { + case DBG_END_SEQUENCE: + return; + + case DBG_ADVANCE_PC: + addrDiff = in.readUleb128(); + debugInfoOut.writeUleb128(addrDiff); + break; + + case DBG_ADVANCE_LINE: + lineDiff = in.readSleb128(); + debugInfoOut.writeSleb128(lineDiff); + break; + + case DBG_START_LOCAL: + case DBG_START_LOCAL_EXTENDED: + registerNum = in.readUleb128(); + debugInfoOut.writeUleb128(registerNum); + nameIndex = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex)); + typeIndex = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustType(typeIndex)); + if (opcode == DBG_START_LOCAL_EXTENDED) { + sigIndex = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustString(sigIndex)); + } + break; + + case DBG_END_LOCAL: + case DBG_RESTART_LOCAL: + registerNum = in.readUleb128(); + debugInfoOut.writeUleb128(registerNum); + break; + + case DBG_SET_FILE: + nameIndex = in.readUleb128p1(); + debugInfoOut.writeUleb128p1(indexMap.adjustString(nameIndex)); + break; + + case DBG_SET_PROLOGUE_END: + case DBG_SET_EPILOGUE_BEGIN: + default: + break; + } + } + } + + private void transformEncodedCatchHandler(Code.CatchHandler catchHandler, IndexMap indexMap) { + int catchAllAddress = catchHandler.getCatchAllAddress(); + int[] typeIndexes = catchHandler.getTypeIndexes(); + int[] addresses = catchHandler.getAddresses(); + + if (catchAllAddress != -1) { + codeOut.writeSleb128(-typeIndexes.length); + } else { + codeOut.writeSleb128(typeIndexes.length); + } + + for (int i = 0; i < typeIndexes.length; i++) { + codeOut.writeUleb128(indexMap.adjustType(typeIndexes[i])); + codeOut.writeUleb128(addresses[i]); + } + + if (catchAllAddress != -1) { + codeOut.writeUleb128(catchAllAddress); + } + } + + private void transformStaticValues(Dex.Section in, IndexMap indexMap) { + contentsOut.encodedArrays.size++; + indexMap.putStaticValuesOffset(in.getPosition(), encodedArrayOut.getPosition()); + indexMap.adjustEncodedArray(in.readEncodedArray()).writeTo(encodedArrayOut); + } + + /** + * Byte counts for the sections written when creating a dex. Target sizes + * are defined in one of two ways: + *
    + *
  • By pessimistically guessing how large the union of dex files will be. + * We're pessimistic because we can't predict the amount of duplication + * between dex files, nor can we predict the length of ULEB-encoded + * offsets or indices. + *
  • By exactly measuring an existing dex. + *
+ */ + private static class WriterSizes { + private int header = SizeOf.HEADER_ITEM; + private int idsDefs; + private int mapList; + private int typeList; + private int classData; + private int code; + private int stringData; + private int debugInfo; + private int encodedArray; + private int annotationsDirectory; + private int annotationsSet; + private int annotationsSetRefList; + private int annotation; + + /** + * Compute sizes for merging several dexes. + */ + public WriterSizes(Dex[] dexes) { + for (int i = 0; i < dexes.length; i++) { + plus(dexes[i].getTableOfContents(), false); + } + fourByteAlign(); + } + + public WriterSizes(DexMerger dexMerger) { + header = dexMerger.headerOut.used(); + idsDefs = dexMerger.idsDefsOut.used(); + mapList = dexMerger.mapListOut.used(); + typeList = dexMerger.typeListOut.used(); + classData = dexMerger.classDataOut.used(); + code = dexMerger.codeOut.used(); + stringData = dexMerger.stringDataOut.used(); + debugInfo = dexMerger.debugInfoOut.used(); + encodedArray = dexMerger.encodedArrayOut.used(); + annotationsDirectory = dexMerger.annotationsDirectoryOut.used(); + annotationsSet = dexMerger.annotationSetOut.used(); + annotationsSetRefList = dexMerger.annotationSetRefListOut.used(); + annotation = dexMerger.annotationOut.used(); + fourByteAlign(); + } + + private void plus(TableOfContents contents, boolean exact) { + idsDefs += contents.stringIds.size * SizeOf.STRING_ID_ITEM + + contents.typeIds.size * SizeOf.TYPE_ID_ITEM + + contents.protoIds.size * SizeOf.PROTO_ID_ITEM + + contents.fieldIds.size * SizeOf.MEMBER_ID_ITEM + + contents.methodIds.size * SizeOf.MEMBER_ID_ITEM + + contents.classDefs.size * SizeOf.CLASS_DEF_ITEM; + mapList = SizeOf.UINT + (contents.sections.length * SizeOf.MAP_ITEM); + typeList += fourByteAlign(contents.typeLists.byteCount); // We count each dex's + // typelists section as realigned on 4 bytes, because each typelist of each dex's + // typelists section is aligned on 4 bytes. If we didn't, there is a case where each + // size of both dex's typelists section is a multiple of 2 but not a multiple of 4, + // and the sum of both sizes is a multiple of 4 but would not be sufficient to write + // each typelist aligned on 4 bytes. + stringData += contents.stringDatas.byteCount; + annotationsDirectory += contents.annotationsDirectories.byteCount; + annotationsSet += contents.annotationSets.byteCount; + annotationsSetRefList += contents.annotationSetRefLists.byteCount; + + if (exact) { + code += contents.codes.byteCount; + classData += contents.classDatas.byteCount; + encodedArray += contents.encodedArrays.byteCount; + annotation += contents.annotations.byteCount; + debugInfo += contents.debugInfos.byteCount; + } else { + // at most 1/4 of the bytes in a code section are uleb/sleb + code += (int) Math.ceil(contents.codes.byteCount * 1.25); + // at most 2/3 of the bytes in a class data section are uleb/sleb that may change + // (assuming the worst case that section contains only methods and no fields) + classData += (int) Math.ceil(contents.classDatas.byteCount * 1.67); + // all of the bytes in an encoding arrays section may be uleb/sleb + encodedArray += contents.encodedArrays.byteCount * 2; + // all of the bytes in an annotations section may be uleb/sleb + annotation += (int) Math.ceil(contents.annotations.byteCount * 2); + // all of the bytes in a debug info section may be uleb/sleb + debugInfo += contents.debugInfos.byteCount * 2; + } + } + + private void fourByteAlign() { + header = fourByteAlign(header); + idsDefs = fourByteAlign(idsDefs); + mapList = fourByteAlign(mapList); + typeList = fourByteAlign(typeList); + classData = fourByteAlign(classData); + code = fourByteAlign(code); + stringData = fourByteAlign(stringData); + debugInfo = fourByteAlign(debugInfo); + encodedArray = fourByteAlign(encodedArray); + annotationsDirectory = fourByteAlign(annotationsDirectory); + annotationsSet = fourByteAlign(annotationsSet); + annotationsSetRefList = fourByteAlign(annotationsSetRefList); + annotation = fourByteAlign(annotation); + } + + private static int fourByteAlign(int position) { + return (position + 3) & ~3; + } + + public int size() { + return header + idsDefs + mapList + typeList + classData + code + stringData + debugInfo + + encodedArray + annotationsDirectory + annotationsSet + annotationsSetRefList + + annotation; + } + } + + public static void main(String[] args) throws IOException { + if (args.length < 2) { + printUsage(); + return; + } + + Dex[] dexes = new Dex[args.length - 1]; + for (int i = 1; i < args.length; i++) { + dexes[i - 1] = new Dex(new File(args[i])); + } + Dex merged = new DexMerger(dexes, CollisionPolicy.KEEP_FIRST, new DxContext()).merge(); + merged.writeTo(new File(args[0])); + } + + private static void printUsage() { + System.out.println("Usage: DexMerger ..."); + System.out.println(); + System.out.println( + "If a class is defined in several dex, the class found in the first dex will be used."); + } +} diff --git a/dexlib/src/main/java/com/android/dx/merge/IndexMap.java b/dexlib/src/main/java/com/android/dx/merge/IndexMap.java new file mode 100644 index 000000000..f47c03ccb --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/merge/IndexMap.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.merge; + +import com.android.dex.Annotation; +import com.android.dex.util.ByteOutput; +import com.android.dex.ClassDef; +import com.android.dex.Dex; +import com.android.dex.DexException; +import com.android.dex.EncodedValue; +import com.android.dex.EncodedValueReader; +import static com.android.dex.EncodedValueReader.ENCODED_ANNOTATION; +import static com.android.dex.EncodedValueReader.ENCODED_ARRAY; +import static com.android.dex.EncodedValueReader.ENCODED_BOOLEAN; +import static com.android.dex.EncodedValueReader.ENCODED_BYTE; +import static com.android.dex.EncodedValueReader.ENCODED_CHAR; +import static com.android.dex.EncodedValueReader.ENCODED_DOUBLE; +import static com.android.dex.EncodedValueReader.ENCODED_ENUM; +import static com.android.dex.EncodedValueReader.ENCODED_FIELD; +import static com.android.dex.EncodedValueReader.ENCODED_FLOAT; +import static com.android.dex.EncodedValueReader.ENCODED_INT; +import static com.android.dex.EncodedValueReader.ENCODED_LONG; +import static com.android.dex.EncodedValueReader.ENCODED_METHOD; +import static com.android.dex.EncodedValueReader.ENCODED_NULL; +import static com.android.dex.EncodedValueReader.ENCODED_SHORT; +import static com.android.dex.EncodedValueReader.ENCODED_STRING; +import static com.android.dex.EncodedValueReader.ENCODED_TYPE; +import com.android.dex.EncodedValueCodec; +import com.android.dex.FieldId; +import com.android.dex.Leb128; +import com.android.dex.MethodId; +import com.android.dex.ProtoId; +import com.android.dex.TableOfContents; +import com.android.dex.TypeList; +import com.android.dx.util.ByteArrayAnnotatedOutput; +import java.util.HashMap; + +/** + * Maps the index offsets from one dex file to those in another. For example, if + * you have string #5 in the old dex file, its position in the new dex file is + * {@code strings[5]}. + */ +public final class IndexMap { + private final Dex target; + public final int[] stringIds; + public final short[] typeIds; + public final short[] protoIds; + public final short[] fieldIds; + public final short[] methodIds; + private final HashMap typeListOffsets; + private final HashMap annotationOffsets; + private final HashMap annotationSetOffsets; + private final HashMap annotationSetRefListOffsets; + private final HashMap annotationDirectoryOffsets; + private final HashMap staticValuesOffsets; + + public IndexMap(Dex target, TableOfContents tableOfContents) { + this.target = target; + this.stringIds = new int[tableOfContents.stringIds.size]; + this.typeIds = new short[tableOfContents.typeIds.size]; + this.protoIds = new short[tableOfContents.protoIds.size]; + this.fieldIds = new short[tableOfContents.fieldIds.size]; + this.methodIds = new short[tableOfContents.methodIds.size]; + this.typeListOffsets = new HashMap(); + this.annotationOffsets = new HashMap(); + this.annotationSetOffsets = new HashMap(); + this.annotationSetRefListOffsets = new HashMap(); + this.annotationDirectoryOffsets = new HashMap(); + this.staticValuesOffsets = new HashMap(); + + /* + * A type list, annotation set, annotation directory, or static value at + * offset 0 is always empty. Always map offset 0 to 0. + */ + this.typeListOffsets.put(0, 0); + this.annotationSetOffsets.put(0, 0); + this.annotationDirectoryOffsets.put(0, 0); + this.staticValuesOffsets.put(0, 0); + } + + public void putTypeListOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + typeListOffsets.put(oldOffset, newOffset); + } + + public void putAnnotationOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + annotationOffsets.put(oldOffset, newOffset); + } + + public void putAnnotationSetOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + annotationSetOffsets.put(oldOffset, newOffset); + } + + public void putAnnotationSetRefListOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + annotationSetRefListOffsets.put(oldOffset, newOffset); + } + + public void putAnnotationDirectoryOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + annotationDirectoryOffsets.put(oldOffset, newOffset); + } + + public void putStaticValuesOffset(int oldOffset, int newOffset) { + if (oldOffset <= 0 || newOffset <= 0) { + throw new IllegalArgumentException(); + } + staticValuesOffsets.put(oldOffset, newOffset); + } + + public int adjustString(int stringIndex) { + return stringIndex == ClassDef.NO_INDEX ? ClassDef.NO_INDEX : stringIds[stringIndex]; + } + + public int adjustType(int typeIndex) { + return (typeIndex == ClassDef.NO_INDEX) ? ClassDef.NO_INDEX : (typeIds[typeIndex] & 0xffff); + } + + public TypeList adjustTypeList(TypeList typeList) { + if (typeList == TypeList.EMPTY) { + return typeList; + } + short[] types = typeList.getTypes().clone(); + for (int i = 0; i < types.length; i++) { + types[i] = (short) adjustType(types[i]); + } + return new TypeList(target, types); + } + + public int adjustProto(int protoIndex) { + return protoIds[protoIndex] & 0xffff; + } + + public int adjustField(int fieldIndex) { + return fieldIds[fieldIndex] & 0xffff; + } + + public int adjustMethod(int methodIndex) { + return methodIds[methodIndex] & 0xffff; + } + + public int adjustTypeListOffset(int typeListOffset) { + return typeListOffsets.get(typeListOffset); + } + + public int adjustAnnotation(int annotationOffset) { + return annotationOffsets.get(annotationOffset); + } + + public int adjustAnnotationSet(int annotationSetOffset) { + return annotationSetOffsets.get(annotationSetOffset); + } + + public int adjustAnnotationSetRefList(int annotationSetRefListOffset) { + return annotationSetRefListOffsets.get(annotationSetRefListOffset); + } + + public int adjustAnnotationDirectory(int annotationDirectoryOffset) { + return annotationDirectoryOffsets.get(annotationDirectoryOffset); + } + + public int adjustStaticValues(int staticValuesOffset) { + return staticValuesOffsets.get(staticValuesOffset); + } + + public MethodId adjust(MethodId methodId) { + return new MethodId(target, + adjustType(methodId.getDeclaringClassIndex()), + adjustProto(methodId.getProtoIndex()), + adjustString(methodId.getNameIndex())); + } + + public FieldId adjust(FieldId fieldId) { + return new FieldId(target, + adjustType(fieldId.getDeclaringClassIndex()), + adjustType(fieldId.getTypeIndex()), + adjustString(fieldId.getNameIndex())); + + } + + public ProtoId adjust(ProtoId protoId) { + return new ProtoId(target, + adjustString(protoId.getShortyIndex()), + adjustType(protoId.getReturnTypeIndex()), + adjustTypeListOffset(protoId.getParametersOffset())); + } + + public ClassDef adjust(ClassDef classDef) { + return new ClassDef(target, classDef.getOffset(), adjustType(classDef.getTypeIndex()), + classDef.getAccessFlags(), adjustType(classDef.getSupertypeIndex()), + adjustTypeListOffset(classDef.getInterfacesOffset()), classDef.getSourceFileIndex(), + classDef.getAnnotationsOffset(), classDef.getClassDataOffset(), + classDef.getStaticValuesOffset()); + } + + public SortableType adjust(SortableType sortableType) { + return new SortableType(sortableType.getDex(), + sortableType.getIndexMap(), adjust(sortableType.getClassDef())); + } + + public EncodedValue adjustEncodedValue(EncodedValue encodedValue) { + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); + new EncodedValueTransformer(out).transform(new EncodedValueReader(encodedValue)); + return new EncodedValue(out.toByteArray()); + } + + public EncodedValue adjustEncodedArray(EncodedValue encodedArray) { + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); + new EncodedValueTransformer(out).transformArray( + new EncodedValueReader(encodedArray, ENCODED_ARRAY)); + return new EncodedValue(out.toByteArray()); + } + + public Annotation adjust(Annotation annotation) { + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(32); + new EncodedValueTransformer(out).transformAnnotation( + annotation.getReader()); + return new Annotation(target, annotation.getVisibility(), + new EncodedValue(out.toByteArray())); + } + + /** + * Adjust an encoded value or array. + */ + private final class EncodedValueTransformer { + private final ByteOutput out; + + public EncodedValueTransformer(ByteOutput out) { + this.out = out; + } + + public void transform(EncodedValueReader reader) { + // TODO: extract this into a helper class, EncodedValueWriter + switch (reader.peek()) { + case ENCODED_BYTE: + EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_BYTE, reader.readByte()); + break; + case ENCODED_SHORT: + EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_SHORT, reader.readShort()); + break; + case ENCODED_INT: + EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_INT, reader.readInt()); + break; + case ENCODED_LONG: + EncodedValueCodec.writeSignedIntegralValue(out, ENCODED_LONG, reader.readLong()); + break; + case ENCODED_CHAR: + EncodedValueCodec.writeUnsignedIntegralValue(out, ENCODED_CHAR, reader.readChar()); + break; + case ENCODED_FLOAT: + // Shift value left 32 so that right-zero-extension works. + long longBits = ((long) Float.floatToIntBits(reader.readFloat())) << 32; + EncodedValueCodec.writeRightZeroExtendedValue(out, ENCODED_FLOAT, longBits); + break; + case ENCODED_DOUBLE: + EncodedValueCodec.writeRightZeroExtendedValue( + out, ENCODED_DOUBLE, Double.doubleToLongBits(reader.readDouble())); + break; + case ENCODED_STRING: + EncodedValueCodec.writeUnsignedIntegralValue( + out, ENCODED_STRING, adjustString(reader.readString())); + break; + case ENCODED_TYPE: + EncodedValueCodec.writeUnsignedIntegralValue( + out, ENCODED_TYPE, adjustType(reader.readType())); + break; + case ENCODED_FIELD: + EncodedValueCodec.writeUnsignedIntegralValue( + out, ENCODED_FIELD, adjustField(reader.readField())); + break; + case ENCODED_ENUM: + EncodedValueCodec.writeUnsignedIntegralValue( + out, ENCODED_ENUM, adjustField(reader.readEnum())); + break; + case ENCODED_METHOD: + EncodedValueCodec.writeUnsignedIntegralValue( + out, ENCODED_METHOD, adjustMethod(reader.readMethod())); + break; + case ENCODED_ARRAY: + writeTypeAndArg(ENCODED_ARRAY, 0); + transformArray(reader); + break; + case ENCODED_ANNOTATION: + writeTypeAndArg(ENCODED_ANNOTATION, 0); + transformAnnotation(reader); + break; + case ENCODED_NULL: + reader.readNull(); + writeTypeAndArg(ENCODED_NULL, 0); + break; + case ENCODED_BOOLEAN: + boolean value = reader.readBoolean(); + writeTypeAndArg(ENCODED_BOOLEAN, value ? 1 : 0); + break; + default: + throw new DexException("Unexpected type: " + Integer.toHexString(reader.peek())); + } + } + + private void transformAnnotation(EncodedValueReader reader) { + int fieldCount = reader.readAnnotation(); + Leb128.writeUnsignedLeb128(out, adjustType(reader.getAnnotationType())); + Leb128.writeUnsignedLeb128(out, fieldCount); + for (int i = 0; i < fieldCount; i++) { + Leb128.writeUnsignedLeb128(out, adjustString(reader.readAnnotationName())); + transform(reader); + } + } + + private void transformArray(EncodedValueReader reader) { + int size = reader.readArray(); + Leb128.writeUnsignedLeb128(out, size); + for (int i = 0; i < size; i++) { + transform(reader); + } + } + + private void writeTypeAndArg(int type, int arg) { + out.writeByte((arg << 5) | type); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/merge/InstructionTransformer.java b/dexlib/src/main/java/com/android/dx/merge/InstructionTransformer.java new file mode 100644 index 000000000..461af0f35 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/merge/InstructionTransformer.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.merge; + +import com.android.dex.DexException; +import com.android.dex.DexIndexOverflowException; +import com.android.dx.io.CodeReader; +import com.android.dx.io.Opcodes; +import com.android.dx.io.instructions.DecodedInstruction; +import com.android.dx.io.instructions.ShortArrayCodeOutput; + +final class InstructionTransformer { + private final CodeReader reader; + + private DecodedInstruction[] mappedInstructions; + private int mappedAt; + private IndexMap indexMap; + + public InstructionTransformer() { + this.reader = new CodeReader(); + this.reader.setAllVisitors(new GenericVisitor()); + this.reader.setStringVisitor(new StringVisitor()); + this.reader.setTypeVisitor(new TypeVisitor()); + this.reader.setFieldVisitor(new FieldVisitor()); + this.reader.setMethodVisitor(new MethodVisitor()); + } + + public short[] transform(IndexMap indexMap, short[] encodedInstructions) throws DexException { + DecodedInstruction[] decodedInstructions = + DecodedInstruction.decodeAll(encodedInstructions); + int size = decodedInstructions.length; + + this.indexMap = indexMap; + mappedInstructions = new DecodedInstruction[size]; + mappedAt = 0; + reader.visitAll(decodedInstructions); + + ShortArrayCodeOutput out = new ShortArrayCodeOutput(size); + for (DecodedInstruction instruction : mappedInstructions) { + if (instruction != null) { + instruction.encode(out); + } + } + + this.indexMap = null; + return out.getArray(); + } + + private class GenericVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + mappedInstructions[mappedAt++] = one; + } + } + + private class StringVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + int stringId = one.getIndex(); + int mappedId = indexMap.adjustString(stringId); + boolean isJumbo = (one.getOpcode() == Opcodes.CONST_STRING_JUMBO); + jumboCheck(isJumbo, mappedId); + mappedInstructions[mappedAt++] = one.withIndex(mappedId); + } + } + + private class FieldVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + int fieldId = one.getIndex(); + int mappedId = indexMap.adjustField(fieldId); + boolean isJumbo = (one.getOpcode() == Opcodes.CONST_STRING_JUMBO); + jumboCheck(isJumbo, mappedId); + mappedInstructions[mappedAt++] = one.withIndex(mappedId); + } + } + + private class TypeVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + int typeId = one.getIndex(); + int mappedId = indexMap.adjustType(typeId); + boolean isJumbo = (one.getOpcode() == Opcodes.CONST_STRING_JUMBO); + jumboCheck(isJumbo, mappedId); + mappedInstructions[mappedAt++] = one.withIndex(mappedId); + } + } + + private class MethodVisitor implements CodeReader.Visitor { + public void visit(DecodedInstruction[] all, DecodedInstruction one) { + int methodId = one.getIndex(); + int mappedId = indexMap.adjustMethod(methodId); + boolean isJumbo = (one.getOpcode() == Opcodes.CONST_STRING_JUMBO); + jumboCheck(isJumbo, mappedId); + mappedInstructions[mappedAt++] = one.withIndex(mappedId); + } + } + + private static void jumboCheck(boolean isJumbo, int newIndex) { + if (!isJumbo && (newIndex > 0xffff)) { + throw new DexIndexOverflowException("Cannot merge new index " + newIndex + + " into a non-jumbo instruction!"); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/merge/SortableType.java b/dexlib/src/main/java/com/android/dx/merge/SortableType.java new file mode 100644 index 000000000..99c5ce1da --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/merge/SortableType.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.merge; + +import com.android.dex.ClassDef; +import com.android.dex.Dex; +import com.android.dex.DexException; +import java.util.Comparator; + +/** + * Name and structure of a type. Used to order types such that each type is + * preceded by its supertype and implemented interfaces. + */ +final class SortableType { + public static final Comparator NULLS_LAST_ORDER = new Comparator() { + public int compare(SortableType a, SortableType b) { + if (a == b) { + return 0; + } + if (b == null) { + return -1; + } + if (a == null) { + return 1; + } + if (a.depth != b.depth) { + return a.depth - b.depth; + } + return a.getTypeIndex() - b.getTypeIndex(); + } + }; + + private final Dex dex; + private final IndexMap indexMap; + private ClassDef classDef; + private int depth = -1; + + public SortableType(Dex dex, IndexMap indexMap, ClassDef classDef) { + this.dex = dex; + this.indexMap = indexMap; + this.classDef = classDef; + } + + public Dex getDex() { + return dex; + } + + public IndexMap getIndexMap() { + return indexMap; + } + + public ClassDef getClassDef() { + return classDef; + } + + public int getTypeIndex() { + return classDef.getTypeIndex(); + } + + /** + * Assigns this type's depth if the depths of its supertype and implemented + * interfaces are known. Returns false if the depth couldn't be computed + * yet. + */ + public boolean tryAssignDepth(SortableType[] types) { + int max; + if (classDef.getSupertypeIndex() == ClassDef.NO_INDEX) { + max = 0; // this is Object.class or an interface + } else if (classDef.getSupertypeIndex() == classDef.getTypeIndex()) { + // This is an invalid class extending itself. + throw new DexException("Class with type index " + classDef.getTypeIndex() + + " extends itself"); + } else { + SortableType sortableSupertype = types[classDef.getSupertypeIndex()]; + if (sortableSupertype == null) { + max = 1; // unknown, so assume it's a root. + } else if (sortableSupertype.depth == -1) { + return false; + } else { + max = sortableSupertype.depth; + } + } + + for (short interfaceIndex : classDef.getInterfaces()) { + SortableType implemented = types[interfaceIndex]; + if (implemented == null) { + max = Math.max(max, 1); // unknown, so assume it's a root. + } else if (implemented.depth == -1) { + return false; + } else { + max = Math.max(max, implemented.depth); + } + } + + depth = max + 1; + return true; + } + + public boolean isDepthAssigned() { + return depth != -1; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/annotation/Annotation.java b/dexlib/src/main/java/com/android/dx/rop/annotation/Annotation.java new file mode 100644 index 000000000..e4af15df1 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/annotation/Annotation.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.annotation; + +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.MutabilityControl; +import com.android.dx.util.ToHuman; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; + +/** + * An annotation on an element of a class. Annotations have an + * associated type and additionally consist of a set of (name, value) + * pairs, where the names are unique. + */ +public final class Annotation extends MutabilityControl + implements Comparable, ToHuman { + /** {@code non-null;} type of the annotation */ + private final CstType type; + + /** {@code non-null;} the visibility of the annotation */ + private final AnnotationVisibility visibility; + + /** {@code non-null;} map from names to {@link NameValuePair} instances */ + private final TreeMap elements; + + /** + * Construct an instance. It initially contains no elements. + * + * @param type {@code non-null;} type of the annotation + * @param visibility {@code non-null;} the visibility of the annotation + */ + public Annotation(CstType type, AnnotationVisibility visibility) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (visibility == null) { + throw new NullPointerException("visibility == null"); + } + + this.type = type; + this.visibility = visibility; + this.elements = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof Annotation)) { + return false; + } + + Annotation otherAnnotation = (Annotation) other; + + if (! (type.equals(otherAnnotation.type) + && (visibility == otherAnnotation.visibility))) { + return false; + } + + return elements.equals(otherAnnotation.elements); + } + + /** {@inheritDoc} */ + public int hashCode() { + int hash = type.hashCode(); + hash = (hash * 31) + elements.hashCode(); + hash = (hash * 31) + visibility.hashCode(); + return hash; + } + + /** {@inheritDoc} */ + public int compareTo(Annotation other) { + int result = type.compareTo(other.type); + + if (result != 0) { + return result; + } + + result = visibility.compareTo(other.visibility); + + if (result != 0) { + return result; + } + + Iterator thisIter = elements.values().iterator(); + Iterator otherIter = other.elements.values().iterator(); + + while (thisIter.hasNext() && otherIter.hasNext()) { + NameValuePair thisOne = thisIter.next(); + NameValuePair otherOne = otherIter.next(); + + result = thisOne.compareTo(otherOne); + if (result != 0) { + return result; + } + } + + if (thisIter.hasNext()) { + return 1; + } else if (otherIter.hasNext()) { + return -1; + } + + return 0; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return toHuman(); + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append(visibility.toHuman()); + sb.append("-annotation "); + sb.append(type.toHuman()); + sb.append(" {"); + + boolean first = true; + for (NameValuePair pair : elements.values()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(pair.getName().toHuman()); + sb.append(": "); + sb.append(pair.getValue().toHuman()); + } + + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the type of this instance. + * + * @return {@code non-null;} the type + */ + public CstType getType() { + return type; + } + + /** + * Gets the visibility of this instance. + * + * @return {@code non-null;} the visibility + */ + public AnnotationVisibility getVisibility() { + return visibility; + } + + /** + * Put an element into the set of (name, value) pairs for this instance. + * If there is a preexisting element with the same name, it will be + * replaced by this method. + * + * @param pair {@code non-null;} the (name, value) pair to place into this instance + */ + public void put(NameValuePair pair) { + throwIfImmutable(); + + if (pair == null) { + throw new NullPointerException("pair == null"); + } + + elements.put(pair.getName(), pair); + } + + /** + * Add an element to the set of (name, value) pairs for this instance. + * It is an error to call this method if there is a preexisting element + * with the same name. + * + * @param pair {@code non-null;} the (name, value) pair to add to this instance + */ + public void add(NameValuePair pair) { + throwIfImmutable(); + + if (pair == null) { + throw new NullPointerException("pair == null"); + } + + CstString name = pair.getName(); + + if (elements.get(name) != null) { + throw new IllegalArgumentException("name already added: " + name); + } + + elements.put(name, pair); + } + + /** + * Gets the set of name-value pairs contained in this instance. The + * result is always unmodifiable. + * + * @return {@code non-null;} the set of name-value pairs + */ + public Collection getNameValuePairs() { + return Collections.unmodifiableCollection(elements.values()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/annotation/AnnotationVisibility.java b/dexlib/src/main/java/com/android/dx/rop/annotation/AnnotationVisibility.java new file mode 100644 index 000000000..c717b8cbe --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/annotation/AnnotationVisibility.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.annotation; + +import com.android.dx.util.ToHuman; + +/** + * Visibility scope of an annotation. + */ +public enum AnnotationVisibility implements ToHuman { + RUNTIME("runtime"), + BUILD("build"), + SYSTEM("system"), + EMBEDDED("embedded"); + + /** {@code non-null;} the human-oriented string representation */ + private final String human; + + /** + * Constructs an instance. + * + * @param human {@code non-null;} the human-oriented string representation + */ + private AnnotationVisibility(String human) { + this.human = human; + } + + /** {@inheritDoc} */ + public String toHuman() { + return human; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/annotation/Annotations.java b/dexlib/src/main/java/com/android/dx/rop/annotation/Annotations.java new file mode 100644 index 000000000..aea4f248e --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/annotation/Annotations.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.annotation; + +import com.android.dx.rop.cst.CstType; +import com.android.dx.util.MutabilityControl; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; + +/** + * List of {@link Annotation} instances. + */ +public final class Annotations extends MutabilityControl + implements Comparable { + /** {@code non-null;} immutable empty instance */ + public static final Annotations EMPTY = new Annotations(); + + static { + EMPTY.setImmutable(); + } + + /** {@code non-null;} map from types to annotations */ + private final TreeMap annotations; + + /** + * Constructs an immutable instance which is the combination of the + * two given instances. The two instances must contain disjoint sets + * of types. + * + * @param a1 {@code non-null;} an instance + * @param a2 {@code non-null;} the other instance + * @return {@code non-null;} the combination + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public static Annotations combine(Annotations a1, Annotations a2) { + Annotations result = new Annotations(); + + result.addAll(a1); + result.addAll(a2); + result.setImmutable(); + + return result; + } + + /** + * Constructs an immutable instance which is the combination of the + * given instance with the given additional annotation. The latter's + * type must not already appear in the former. + * + * @param annotations {@code non-null;} the instance to augment + * @param annotation {@code non-null;} the additional annotation + * @return {@code non-null;} the combination + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public static Annotations combine(Annotations annotations, + Annotation annotation) { + Annotations result = new Annotations(); + + result.addAll(annotations); + result.add(annotation); + result.setImmutable(); + + return result; + } + + /** + * Constructs an empty instance. + */ + public Annotations() { + annotations = new TreeMap(); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotations.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof Annotations)) { + return false; + } + + Annotations otherAnnotations = (Annotations) other; + + return annotations.equals(otherAnnotations.annotations); + } + + /** {@inheritDoc} */ + public int compareTo(Annotations other) { + Iterator thisIter = annotations.values().iterator(); + Iterator otherIter = other.annotations.values().iterator(); + + while (thisIter.hasNext() && otherIter.hasNext()) { + Annotation thisOne = thisIter.next(); + Annotation otherOne = otherIter.next(); + + int result = thisOne.compareTo(otherOne); + if (result != 0) { + return result; + } + } + + if (thisIter.hasNext()) { + return 1; + } else if (otherIter.hasNext()) { + return -1; + } + + return 0; + } + + /** {@inheritDoc} */ + public String toString() { + StringBuilder sb = new StringBuilder(); + boolean first = true; + + sb.append("annotations{"); + + for (Annotation a : annotations.values()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(a.toHuman()); + } + + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the number of elements in this instance. + * + * @return {@code >= 0;} the size + */ + public int size() { + return annotations.size(); + } + + /** + * Adds an element to this instance. There must not already be an + * element of the same type. + * + * @param annotation {@code non-null;} the element to add + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public void add(Annotation annotation) { + throwIfImmutable(); + + if (annotation == null) { + throw new NullPointerException("annotation == null"); + } + + CstType type = annotation.getType(); + + if (annotations.containsKey(type)) { + throw new IllegalArgumentException("duplicate type: " + + type.toHuman()); + } + + annotations.put(type, annotation); + } + + /** + * Adds all of the elements of the given instance to this one. The + * instances must not have any duplicate types. + * + * @param toAdd {@code non-null;} the annotations to add + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public void addAll(Annotations toAdd) { + throwIfImmutable(); + + if (toAdd == null) { + throw new NullPointerException("toAdd == null"); + } + + for (Annotation a : toAdd.annotations.values()) { + add(a); + } + } + + /** + * Gets the set of annotations contained in this instance. The + * result is always unmodifiable. + * + * @return {@code non-null;} the set of annotations + */ + public Collection getAnnotations() { + return Collections.unmodifiableCollection(annotations.values()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/annotation/AnnotationsList.java b/dexlib/src/main/java/com/android/dx/rop/annotation/AnnotationsList.java new file mode 100644 index 000000000..b97b385a0 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/annotation/AnnotationsList.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.annotation; + +import com.android.dx.util.FixedSizeList; + +/** + * List of {@link Annotations} instances. + */ +public final class AnnotationsList + extends FixedSizeList { + /** {@code non-null;} immutable empty instance */ + public static final AnnotationsList EMPTY = new AnnotationsList(0); + + /** + * Constructs an immutable instance which is the combination of + * the two given instances. The two instances must each have the + * same number of elements, and each pair of elements must contain + * disjoint sets of types. + * + * @param list1 {@code non-null;} an instance + * @param list2 {@code non-null;} the other instance + * @return {@code non-null;} the combination + */ + public static AnnotationsList combine(AnnotationsList list1, + AnnotationsList list2) { + int size = list1.size(); + + if (size != list2.size()) { + throw new IllegalArgumentException("list1.size() != list2.size()"); + } + + AnnotationsList result = new AnnotationsList(size); + + for (int i = 0; i < size; i++) { + Annotations a1 = list1.get(i); + Annotations a2 = list2.get(i); + result.set(i, Annotations.combine(a1, a2)); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public AnnotationsList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Annotations get(int n) { + return (Annotations) get0(n); + } + + /** + * Sets the element at the given index. The given element must be + * immutable. + * + * @param n {@code >= 0, < size();} which index + * @param a {@code null-ok;} the element to set at {@code n} + */ + public void set(int n, Annotations a) { + a.throwIfMutable(); + set0(n, a); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/annotation/NameValuePair.java b/dexlib/src/main/java/com/android/dx/rop/annotation/NameValuePair.java new file mode 100644 index 000000000..56c49365e --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/annotation/NameValuePair.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.annotation; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstString; + +/** + * A (name, value) pair. These are used as the contents of an annotation. + */ +public final class NameValuePair implements Comparable { + /** {@code non-null;} the name */ + private final CstString name; + + /** {@code non-null;} the value */ + private final Constant value; + + /** + * Construct an instance. + * + * @param name {@code non-null;} the name + * @param value {@code non-null;} the value + */ + public NameValuePair(CstString name, Constant value) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (value == null) { + throw new NullPointerException("value == null"); + } + + this.name = name; + this.value = value; + } + + /** {@inheritDoc} */ + public String toString() { + return name.toHuman() + ":" + value; + } + + /** {@inheritDoc} */ + public int hashCode() { + return name.hashCode() * 31 + value.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof NameValuePair)) { + return false; + } + + NameValuePair otherPair = (NameValuePair) other; + + return name.equals(otherPair.name) + && value.equals(otherPair.value); + } + + /** + * {@inheritDoc} + * + *

Instances of this class compare in name-major and value-minor + * order.

+ */ + public int compareTo(NameValuePair other) { + int result = name.compareTo(other.name); + + if (result != 0) { + return result; + } + + return value.compareTo(other.value); + } + + /** + * Gets the name. + * + * @return {@code non-null;} the name + */ + public CstString getName() { + return name; + } + + /** + * Gets the value. + * + * @return {@code non-null;} the value + */ + public Constant getValue() { + return value; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/AccessFlags.java b/dexlib/src/main/java/com/android/dx/rop/code/AccessFlags.java new file mode 100644 index 000000000..f516950a3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/AccessFlags.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.util.Hex; + +/** + * Constants used as "access flags" in various places in classes, and + * related utilities. Although, at the rop layer, flags are generally + * ignored, this is the layer of communication, and as such, this + * package is where these definitions belong. The flag definitions are + * identical to Java access flags, but {@code ACC_SUPER} isn't + * used at all in translated code, and {@code ACC_SYNCHRONIZED} + * is only used in a very limited way. + */ +public final class AccessFlags { + /** public member / class */ + public static final int ACC_PUBLIC = 0x0001; + + /** private member */ + public static final int ACC_PRIVATE = 0x0002; + + /** protected member */ + public static final int ACC_PROTECTED = 0x0004; + + /** static member */ + public static final int ACC_STATIC = 0x0008; + + /** final member / class */ + public static final int ACC_FINAL = 0x0010; + + /** + * synchronized method; only valid in dex files for {@code native} + * methods + */ + public static final int ACC_SYNCHRONIZED = 0x0020; + + /** + * class with new-style {@code invokespecial} for superclass + * method access + */ + public static final int ACC_SUPER = 0x0020; + + /** volatile field */ + public static final int ACC_VOLATILE = 0x0040; + + /** bridge method (generated) */ + public static final int ACC_BRIDGE = 0x0040; + + /** transient field */ + public static final int ACC_TRANSIENT = 0x0080; + + /** varargs method */ + public static final int ACC_VARARGS = 0x0080; + + /** native method */ + public static final int ACC_NATIVE = 0x0100; + + /** "class" is in fact an public static final interface */ + public static final int ACC_INTERFACE = 0x0200; + + /** abstract method / class */ + public static final int ACC_ABSTRACT = 0x0400; + + /** + * method with strict floating point ({@code strictfp}) + * behavior + */ + public static final int ACC_STRICT = 0x0800; + + /** synthetic member */ + public static final int ACC_SYNTHETIC = 0x1000; + + /** class is an annotation type */ + public static final int ACC_ANNOTATION = 0x2000; + + /** + * class is an enumerated type; field is an element of an enumerated + * type + */ + public static final int ACC_ENUM = 0x4000; + + /** method is a constructor */ + public static final int ACC_CONSTRUCTOR = 0x10000; + + /** + * method was declared {@code synchronized}; has no effect on + * execution (other than inspecting this flag, per se) + */ + public static final int ACC_DECLARED_SYNCHRONIZED = 0x20000; + + /** flags defined on classes */ + public static final int CLASS_FLAGS = + ACC_PUBLIC | ACC_FINAL | ACC_SUPER | ACC_INTERFACE | ACC_ABSTRACT | + ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM; + + /** flags defined on inner classes */ + public static final int INNER_CLASS_FLAGS = + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | + ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | + ACC_ENUM; + + /** flags defined on fields */ + public static final int FIELD_FLAGS = + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | + ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM; + + /** flags defined on methods */ + public static final int METHOD_FLAGS = + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | + ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE | + ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR | + ACC_DECLARED_SYNCHRONIZED; + + /** indicates conversion of class flags */ + private static final int CONV_CLASS = 1; + + /** indicates conversion of field flags */ + private static final int CONV_FIELD = 2; + + /** indicates conversion of method flags */ + private static final int CONV_METHOD = 3; + + /** + * This class is uninstantiable. + */ + private AccessFlags() { + // This space intentionally left blank. + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on classes (not fields or methods). + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String classString(int flags) { + return humanHelper(flags, CLASS_FLAGS, CONV_CLASS); + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on inner classes. + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String innerClassString(int flags) { + return humanHelper(flags, INNER_CLASS_FLAGS, CONV_CLASS); + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on fields (not classes or methods). + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String fieldString(int flags) { + return humanHelper(flags, FIELD_FLAGS, CONV_FIELD); + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on methods (not classes or fields). + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String methodString(int flags) { + return humanHelper(flags, METHOD_FLAGS, CONV_METHOD); + } + + /** + * Returns whether the flag {@code ACC_PUBLIC} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_PUBLIC} flag + */ + public static boolean isPublic(int flags) { + return (flags & ACC_PUBLIC) != 0; + } + + /** + * Returns whether the flag {@code ACC_PROTECTED} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_PROTECTED} flag + */ + public static boolean isProtected(int flags) { + return (flags & ACC_PROTECTED) != 0; + } + + /** + * Returns whether the flag {@code ACC_PRIVATE} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_PRIVATE} flag + */ + public static boolean isPrivate(int flags) { + return (flags & ACC_PRIVATE) != 0; + } + + /** + * Returns whether the flag {@code ACC_STATIC} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_STATIC} flag + */ + public static boolean isStatic(int flags) { + return (flags & ACC_STATIC) != 0; + } + + /** + * Returns whether the flag {@code ACC_CONSTRUCTOR} is on in + * the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_CONSTRUCTOR} flag + */ + public static boolean isConstructor(int flags) { + return (flags & ACC_CONSTRUCTOR) != 0; + } + + /** + * Returns whether the flag {@code ACC_INTERFACE} is on in + * the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_INTERFACE} flag + */ + public static boolean isInterface(int flags) { + return (flags & ACC_INTERFACE) != 0; + } + + /** + * Returns whether the flag {@code ACC_SYNCHRONIZED} is on in + * the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_SYNCHRONIZED} flag + */ + public static boolean isSynchronized(int flags) { + return (flags & ACC_SYNCHRONIZED) != 0; + } + + /** + * Returns whether the flag {@code ACC_ABSTRACT} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_ABSTRACT} flag + */ + public static boolean isAbstract(int flags) { + return (flags & ACC_ABSTRACT) != 0; + } + + /** + * Returns whether the flag {@code ACC_NATIVE} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_NATIVE} flag + */ + public static boolean isNative(int flags) { + return (flags & ACC_NATIVE) != 0; + } + + /** + * Returns whether the flag {@code ACC_ANNOTATION} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_ANNOTATION} flag + */ + public static boolean isAnnotation(int flags) { + return (flags & ACC_ANNOTATION) != 0; + } + + /** + * Returns whether the flag {@code ACC_DECLARED_SYNCHRONIZED} is + * on in the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_DECLARED_SYNCHRONIZED} flag + */ + public static boolean isDeclaredSynchronized(int flags) { + return (flags & ACC_DECLARED_SYNCHRONIZED) != 0; + } + + /** + * Returns whether the flag {@code ACC_ENUM} is on in the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_ENUM} flag + */ + public static boolean isEnum(int flags) { + return (flags & ACC_ENUM) != 0; + } + + /** + * Helper to return a human-oriented string representing the given + * access flags. + * + * @param flags the defined flags + * @param mask mask for the "defined" bits + * @param what what the flags represent (one of {@code CONV_*}) + * @return {@code non-null;} human-oriented string + */ + private static String humanHelper(int flags, int mask, int what) { + StringBuffer sb = new StringBuffer(80); + int extra = flags & ~mask; + + flags &= mask; + + if ((flags & ACC_PUBLIC) != 0) { + sb.append("|public"); + } + if ((flags & ACC_PRIVATE) != 0) { + sb.append("|private"); + } + if ((flags & ACC_PROTECTED) != 0) { + sb.append("|protected"); + } + if ((flags & ACC_STATIC) != 0) { + sb.append("|static"); + } + if ((flags & ACC_FINAL) != 0) { + sb.append("|final"); + } + if ((flags & ACC_SYNCHRONIZED) != 0) { + if (what == CONV_CLASS) { + sb.append("|super"); + } else { + sb.append("|synchronized"); + } + } + if ((flags & ACC_VOLATILE) != 0) { + if (what == CONV_METHOD) { + sb.append("|bridge"); + } else { + sb.append("|volatile"); + } + } + if ((flags & ACC_TRANSIENT) != 0) { + if (what == CONV_METHOD) { + sb.append("|varargs"); + } else { + sb.append("|transient"); + } + } + if ((flags & ACC_NATIVE) != 0) { + sb.append("|native"); + } + if ((flags & ACC_INTERFACE) != 0) { + sb.append("|interface"); + } + if ((flags & ACC_ABSTRACT) != 0) { + sb.append("|abstract"); + } + if ((flags & ACC_STRICT) != 0) { + sb.append("|strictfp"); + } + if ((flags & ACC_SYNTHETIC) != 0) { + sb.append("|synthetic"); + } + if ((flags & ACC_ANNOTATION) != 0) { + sb.append("|annotation"); + } + if ((flags & ACC_ENUM) != 0) { + sb.append("|enum"); + } + if ((flags & ACC_CONSTRUCTOR) != 0) { + sb.append("|constructor"); + } + if ((flags & ACC_DECLARED_SYNCHRONIZED) != 0) { + sb.append("|declared_synchronized"); + } + + if ((extra != 0) || (sb.length() == 0)) { + sb.append('|'); + sb.append(Hex.u2(extra)); + } + + return sb.substring(1); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/BasicBlock.java b/dexlib/src/main/java/com/android/dx/rop/code/BasicBlock.java new file mode 100644 index 000000000..9d99833a6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/BasicBlock.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.Hex; +import com.android.dx.util.IntList; +import com.android.dx.util.LabeledItem; + +/** + * Basic block of register-based instructions. + */ +public final class BasicBlock implements LabeledItem { + /** {@code >= 0;} target label for this block */ + private final int label; + + /** {@code non-null;} list of instructions in this block */ + private final InsnList insns; + + /** + * {@code non-null;} full list of successors that this block may + * branch to + */ + private final IntList successors; + + /** + * {@code >= -1;} the primary / standard-flow / "default" successor, or + * {@code -1} if this block has no successors (that is, it + * exits the function/method) + */ + private final int primarySuccessor; + + /** + * Constructs an instance. The predecessor set is set to {@code null}. + * + * @param label {@code >= 0;} target label for this block + * @param insns {@code non-null;} list of instructions in this block + * @param successors {@code non-null;} full list of successors that this + * block may branch to + * @param primarySuccessor {@code >= -1;} the primary / standard-flow / + * "default" successor, or {@code -1} if this block has no + * successors (that is, it exits the function/method or is an + * unconditional throw) + */ + public BasicBlock(int label, InsnList insns, IntList successors, + int primarySuccessor) { + if (label < 0) { + throw new IllegalArgumentException("label < 0"); + } + + try { + insns.throwIfMutable(); + } catch (NullPointerException ex) { + // Elucidate exception. + throw new NullPointerException("insns == null"); + } + + int sz = insns.size(); + + if (sz == 0) { + throw new IllegalArgumentException("insns.size() == 0"); + } + + for (int i = sz - 2; i >= 0; i--) { + Rop one = insns.get(i).getOpcode(); + if (one.getBranchingness() != Rop.BRANCH_NONE) { + throw new IllegalArgumentException("insns[" + i + "] is a " + + "branch or can throw"); + } + } + + Insn lastInsn = insns.get(sz - 1); + if (lastInsn.getOpcode().getBranchingness() == Rop.BRANCH_NONE) { + throw new IllegalArgumentException("insns does not end with " + + "a branch or throwing " + + "instruction"); + } + + try { + successors.throwIfMutable(); + } catch (NullPointerException ex) { + // Elucidate exception. + throw new NullPointerException("successors == null"); + } + + if (primarySuccessor < -1) { + throw new IllegalArgumentException("primarySuccessor < -1"); + } + + if (primarySuccessor >= 0 && !successors.contains(primarySuccessor)) { + throw new IllegalArgumentException( + "primarySuccessor " + primarySuccessor + " not in successors " + successors); + } + + this.label = label; + this.insns = insns; + this.successors = successors; + this.primarySuccessor = primarySuccessor; + } + + /** + * {@inheritDoc} + * + * Instances of this class compare by identity. That is, + * {@code x.equals(y)} is only true if {@code x == y}. + */ + @Override + public boolean equals(Object other) { + return (this == other); + } + + /** + * {@inheritDoc} + * + * Return the identity hashcode of this instance. This is proper, + * since instances of this class compare by identity (see {@link #equals}). + */ + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + /** + * Gets the target label of this block. + * + * @return {@code >= 0;} the label + */ + public int getLabel() { + return label; + } + + /** + * Gets the list of instructions inside this block. + * + * @return {@code non-null;} the instruction list + */ + public InsnList getInsns() { + return insns; + } + + /** + * Gets the list of successors that this block may branch to. + * + * @return {@code non-null;} the successors list + */ + public IntList getSuccessors() { + return successors; + } + + /** + * Gets the primary successor of this block. + * + * @return {@code >= -1;} the primary successor, or {@code -1} if this + * block has no successors at all + */ + public int getPrimarySuccessor() { + return primarySuccessor; + } + + /** + * Gets the secondary successor of this block. It is only valid to call + * this method on blocks that have exactly two successors. + * + * @return {@code >= 0;} the secondary successor + */ + public int getSecondarySuccessor() { + if (successors.size() != 2) { + throw new UnsupportedOperationException( + "block doesn't have exactly two successors"); + } + + int succ = successors.get(0); + if (succ == primarySuccessor) { + succ = successors.get(1); + } + + return succ; + } + + /** + * Gets the first instruction of this block. This is just a + * convenient shorthand for {@code getInsns().get(0)}. + * + * @return {@code non-null;} the first instruction + */ + public Insn getFirstInsn() { + return insns.get(0); + } + + /** + * Gets the last instruction of this block. This is just a + * convenient shorthand for {@code getInsns().getLast()}. + * + * @return {@code non-null;} the last instruction + */ + public Insn getLastInsn() { + return insns.getLast(); + } + + /** + * Returns whether this block might throw an exception. This is + * just a convenient shorthand for {@code getLastInsn().canThrow()}. + * + * @return {@code true} iff this block might throw an + * exception + */ + public boolean canThrow() { + return insns.getLast().canThrow(); + } + + /** + * Returns whether this block has any associated exception handlers. + * This is just a shorthand for inspecting the last instruction in + * the block to see if it could throw, and if so, whether it in fact + * has any associated handlers. + * + * @return {@code true} iff this block has any associated + * exception handlers + */ + public boolean hasExceptionHandlers() { + Insn lastInsn = insns.getLast(); + return lastInsn.getCatches().size() != 0; + } + + /** + * Returns the exception handler types associated with this block, + * if any. This is just a shorthand for inspecting the last + * instruction in the block to see if it could throw, and if so, + * grabbing the catch list out of it. If not, this returns an + * empty list (not {@code null}). + * + * @return {@code non-null;} the exception handler types associated with + * this block + */ + public TypeList getExceptionHandlerTypes() { + Insn lastInsn = insns.getLast(); + return lastInsn.getCatches(); + } + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public BasicBlock withRegisterOffset(int delta) { + return new BasicBlock(label, insns.withRegisterOffset(delta), + successors, primarySuccessor); + } + + public String toString() { + return '{' + Hex.u2(label) + '}'; + } + + /** + * BasicBlock visitor interface + */ + public interface Visitor { + /** + * Visits a basic block + * @param b block visited + */ + public void visitBlock (BasicBlock b); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/BasicBlockList.java b/dexlib/src/main/java/com/android/dx/rop/code/BasicBlockList.java new file mode 100644 index 000000000..56d1482d3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/BasicBlockList.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.Hex; +import com.android.dx.util.IntList; +import com.android.dx.util.LabeledList; + +/** + * List of {@link BasicBlock} instances. + */ +public final class BasicBlockList extends LabeledList { + /** + * {@code >= -1;} the count of registers required by this method or + * {@code -1} if not yet calculated + */ + private int regCount; + + /** + * Constructs an instance. All indices initially contain {@code null}, + * and the first-block label is initially {@code -1}. + * + * @param size the size of the list + */ + public BasicBlockList(int size) { + super(size); + + regCount = -1; + } + + /** + * Constructs a mutable copy for {@code getMutableCopy()}. + * + * @param old block to copy + */ + private BasicBlockList(BasicBlockList old) { + super(old); + regCount = old.regCount; + } + + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public BasicBlock get(int n) { + return (BasicBlock) get0(n); + } + + /** + * Sets the basic block at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param bb {@code null-ok;} the element to set at {@code n} + */ + public void set(int n, BasicBlock bb) { + super.set(n, bb); + + // Reset regCount, since it will need to be recalculated. + regCount = -1; + } + + /** + * Returns how many registers this method requires. This is simply + * the maximum of register-number-plus-category referred to by this + * instance's instructions (indirectly through {@link BasicBlock} + * instances). + * + * @return {@code >= 0;} the register count + */ + public int getRegCount() { + if (regCount == -1) { + RegCountVisitor visitor = new RegCountVisitor(); + forEachInsn(visitor); + regCount = visitor.getRegCount(); + } + + return regCount; + } + + /** + * Gets the total instruction count for this instance. This is the + * sum of the instruction counts of each block. + * + * @return {@code >= 0;} the total instruction count + */ + public int getInstructionCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + BasicBlock one = (BasicBlock) getOrNull0(i); + if (one != null) { + result += one.getInsns().size(); + } + } + + return result; + } + + /** + * Gets the total instruction count for this instance, ignoring + * mark-local instructions which are not actually emitted. + * + * @return {@code >= 0;} the total instruction count + */ + public int getEffectiveInstructionCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + BasicBlock one = (BasicBlock) getOrNull0(i); + if (one != null) { + InsnList insns = one.getInsns(); + int insnsSz = insns.size(); + + for (int j = 0; j < insnsSz; j++) { + Insn insn = insns.get(j); + + if (insn.getOpcode().getOpcode() != RegOps.MARK_LOCAL) { + result++; + } + } + } + } + + return result; + } + + /** + * Gets the first block in the list with the given label, if any. + * + * @param label {@code label >= 0;} the label to look for + * @return {@code non-null;} the so-labelled block + * @throws IllegalArgumentException thrown if the label isn't found + */ + public BasicBlock labelToBlock(int label) { + int idx = indexOfLabel(label); + + if (idx < 0) { + throw new IllegalArgumentException("no such label: " + + Hex.u2(label)); + } + + return get(idx); + } + + /** + * Visits each instruction of each block in the list, in order. + * + * @param visitor {@code non-null;} visitor to use + */ + public void forEachInsn(Insn.Visitor visitor) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + BasicBlock one = get(i); + InsnList insns = one.getInsns(); + insns.forEach(visitor); + } + } + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. Mutability of the result is inherited from the + * original. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public BasicBlockList withRegisterOffset(int delta) { + int sz = size(); + BasicBlockList result = new BasicBlockList(sz); + + for (int i = 0; i < sz; i++) { + BasicBlock one = (BasicBlock) get0(i); + if (one != null) { + result.set(i, one.withRegisterOffset(delta)); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a mutable copy of this list. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public BasicBlockList getMutableCopy() { + return new BasicBlockList(this); + } + + /** + * Gets the preferred successor for the given block. If the block + * only has one successor, then that is the preferred successor. + * Otherwise, if the block has a primay successor, then that is + * the preferred successor. If the block has no successors, then + * this returns {@code null}. + * + * @param block {@code non-null;} the block in question + * @return {@code null-ok;} the preferred successor, if any + */ + public BasicBlock preferredSuccessorOf(BasicBlock block) { + int primarySuccessor = block.getPrimarySuccessor(); + IntList successors = block.getSuccessors(); + int succSize = successors.size(); + + switch (succSize) { + case 0: { + return null; + } + case 1: { + return labelToBlock(successors.get(0)); + } + } + + if (primarySuccessor != -1) { + return labelToBlock(primarySuccessor); + } else { + return labelToBlock(successors.get(0)); + } + } + + /** + * Compares the catches of two blocks for equality. This includes + * both the catch types and target labels. + * + * @param block1 {@code non-null;} one block to compare + * @param block2 {@code non-null;} the other block to compare + * @return {@code true} if the two blocks' non-primary successors + * are identical + */ + public boolean catchesEqual(BasicBlock block1, BasicBlock block2) { + TypeList catches1 = block1.getExceptionHandlerTypes(); + TypeList catches2 = block2.getExceptionHandlerTypes(); + + if (!StdTypeList.equalContents(catches1, catches2)) { + return false; + } + + IntList succ1 = block1.getSuccessors(); + IntList succ2 = block2.getSuccessors(); + int size = succ1.size(); // Both are guaranteed to be the same size. + + int primary1 = block1.getPrimarySuccessor(); + int primary2 = block2.getPrimarySuccessor(); + + if (((primary1 == -1) || (primary2 == -1)) + && (primary1 != primary2)) { + /* + * For the current purpose, both blocks in question must + * either both have a primary or both not have a primary to + * be considered equal, and it turns out here that that's not + * the case. + */ + return false; + } + + for (int i = 0; i < size; i++) { + int label1 = succ1.get(i); + int label2 = succ2.get(i); + + if (label1 == primary1) { + /* + * It should be the case that block2's primary is at the + * same index. If not, we consider the blocks unequal for + * the current purpose. + */ + if (label2 != primary2) { + return false; + } + continue; + } + + if (label1 != label2) { + return false; + } + } + + return true; + } + + /** + * Instruction visitor class for counting registers used. + */ + private static class RegCountVisitor + implements Insn.Visitor { + /** {@code >= 0;} register count in-progress */ + private int regCount; + + /** + * Constructs an instance. + */ + public RegCountVisitor() { + regCount = 0; + } + + /** + * Gets the register count. + * + * @return {@code >= 0;} the count + */ + public int getRegCount() { + return regCount; + } + + /** {@inheritDoc} */ + public void visitPlainInsn(PlainInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitPlainCstInsn(PlainCstInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitSwitchInsn(SwitchInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitThrowingInsn(ThrowingInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitInvokePolymorphicInsn(InvokePolymorphicInsn insn) { + visit(insn); + } + + /** + * Helper for all the {@code visit*} methods. + * + * @param insn {@code non-null;} instruction being visited + */ + private void visit(Insn insn) { + RegisterSpec result = insn.getResult(); + + if (result != null) { + processReg(result); + } + + RegisterSpecList sources = insn.getSources(); + int sz = sources.size(); + + for (int i = 0; i < sz; i++) { + processReg(sources.get(i)); + } + } + + /** + * Processes the given register spec. + * + * @param spec {@code non-null;} the register spec + */ + private void processReg(RegisterSpec spec) { + int reg = spec.getNextReg(); + + if (reg > regCount) { + regCount = reg; + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/ConservativeTranslationAdvice.java b/dexlib/src/main/java/com/android/dx/rop/code/ConservativeTranslationAdvice.java new file mode 100644 index 000000000..6c48acf6d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/ConservativeTranslationAdvice.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +/** + * Implementation of {@link TranslationAdvice} which conservatively answers + * {@code false} to all methods. + */ +public final class ConservativeTranslationAdvice + implements TranslationAdvice { + /** {@code non-null;} standard instance of this class */ + public static final ConservativeTranslationAdvice THE_ONE = + new ConservativeTranslationAdvice(); + + /** + * This class is not publicly instantiable. Use {@link #THE_ONE}. + */ + private ConservativeTranslationAdvice() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public boolean hasConstantOperation(Rop opcode, + RegisterSpec sourceA, RegisterSpec sourceB) { + return false; + } + + /** {@inheritDoc} */ + public boolean requiresSourcesInOrder(Rop opcode, + RegisterSpecList sources) { + return false; + } + + /** {@inheritDoc} */ + public int getMaxOptimalRegisterCount() { + return Integer.MAX_VALUE; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/CstInsn.java b/dexlib/src/main/java/com/android/dx/rop/code/CstInsn.java new file mode 100644 index 000000000..d7de2f4be --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/CstInsn.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.Constant; + +/** + * Instruction which contains an explicit reference to a constant. + */ +public abstract class CstInsn + extends Insn { + /** {@code non-null;} the constant */ + private final Constant cst; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + * @param cst {@code non-null;} constant + */ + public CstInsn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpecList sources, Constant cst) { + super(opcode, position, result, sources); + + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + this.cst = cst; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return cst.toHuman(); + } + + /** + * Gets the constant. + * + * @return {@code non-null;} the constant + */ + public Constant getConstant() { + return cst; + } + + /** {@inheritDoc} */ + @Override + public boolean contentEquals(Insn b) { + /* + * The cast (CstInsn)b below should always succeed since + * Insn.contentEquals compares classes of this and b. + */ + return super.contentEquals(b) + && cst.equals(((CstInsn)b).getConstant()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/DexTranslationAdvice.java b/dexlib/src/main/java/com/android/dx/rop/code/DexTranslationAdvice.java new file mode 100644 index 000000000..35ce2f294 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/DexTranslationAdvice.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.type.Type; + +/** + * Implementation of {@link TranslationAdvice} which represents what + * the dex format will be able to represent. + */ +public final class DexTranslationAdvice + implements TranslationAdvice { + /** {@code non-null;} standard instance of this class */ + public static final DexTranslationAdvice THE_ONE = + new DexTranslationAdvice(); + + /** debug advice for disabling invoke-range optimization */ + public static final DexTranslationAdvice NO_SOURCES_IN_ORDER = + new DexTranslationAdvice(true); + + /** + * The minimum source width, in register units, for an invoke + * instruction that requires its sources to be in order and contiguous. + */ + private static final int MIN_INVOKE_IN_ORDER = 6; + + /** when true: always returns false for requiresSourcesInOrder */ + private final boolean disableSourcesInOrder; + + /** + * This class is not publicly instantiable. Use {@link #THE_ONE}. + */ + private DexTranslationAdvice() { + disableSourcesInOrder = false; + } + + private DexTranslationAdvice(boolean disableInvokeRange) { + this.disableSourcesInOrder = disableInvokeRange; + } + + /** {@inheritDoc} */ + public boolean hasConstantOperation(Rop opcode, + RegisterSpec sourceA, RegisterSpec sourceB) { + if (sourceA.getType() != Type.INT) { + return false; + } + + // Return false if second source isn't a constant + if (! (sourceB.getTypeBearer() instanceof CstInteger)) { + // Except for rsub-int (reverse sub) where first source is constant + if (sourceA.getTypeBearer() instanceof CstInteger && + opcode.getOpcode() == RegOps.SUB) { + CstInteger cst = (CstInteger) sourceA.getTypeBearer(); + return cst.fitsIn16Bits(); + } else { + return false; + } + } + + CstInteger cst = (CstInteger) sourceB.getTypeBearer(); + + switch (opcode.getOpcode()) { + // These have 8 and 16 bit cst representations + case RegOps.REM: + case RegOps.ADD: + case RegOps.MUL: + case RegOps.DIV: + case RegOps.AND: + case RegOps.OR: + case RegOps.XOR: + return cst.fitsIn16Bits(); + // These only have 8 bit cst reps + case RegOps.SHL: + case RegOps.SHR: + case RegOps.USHR: + return cst.fitsIn8Bits(); + // No sub-const insn, so check if equivalent add-const fits + case RegOps.SUB: + CstInteger cst2 = CstInteger.make(-cst.getValue()); + return cst2.fitsIn16Bits(); + default: + return false; + } + } + + /** {@inheritDoc} */ + public boolean requiresSourcesInOrder(Rop opcode, + RegisterSpecList sources) { + + return !disableSourcesInOrder && opcode.isCallLike() + && totalRopWidth(sources) >= MIN_INVOKE_IN_ORDER; + } + + /** + * Calculates the total rop width of the list of SSA registers + * + * @param sources {@code non-null;} list of SSA registers + * @return {@code >= 0;} rop-form width in register units + */ + private int totalRopWidth(RegisterSpecList sources) { + int sz = sources.size(); + int total = 0; + + for (int i = 0; i < sz; i++) { + total += sources.get(i).getCategory(); + } + + return total; + } + + /** {@inheritDoc} */ + public int getMaxOptimalRegisterCount() { + return 16; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/Exceptions.java b/dexlib/src/main/java/com/android/dx/rop/code/Exceptions.java new file mode 100644 index 000000000..1e27a8c17 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/Exceptions.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; + +/** + * Common exception types. + */ +public final class Exceptions { + /** {@code non-null;} the type {@code java.lang.ArithmeticException} */ + public static final Type TYPE_ArithmeticException = + Type.intern("Ljava/lang/ArithmeticException;"); + + /** + * {@code non-null;} the type + * {@code java.lang.ArrayIndexOutOfBoundsException} + */ + public static final Type TYPE_ArrayIndexOutOfBoundsException = + Type.intern("Ljava/lang/ArrayIndexOutOfBoundsException;"); + + /** {@code non-null;} the type {@code java.lang.ArrayStoreException} */ + public static final Type TYPE_ArrayStoreException = + Type.intern("Ljava/lang/ArrayStoreException;"); + + /** {@code non-null;} the type {@code java.lang.ClassCastException} */ + public static final Type TYPE_ClassCastException = + Type.intern("Ljava/lang/ClassCastException;"); + + /** {@code non-null;} the type {@code java.lang.Error} */ + public static final Type TYPE_Error = Type.intern("Ljava/lang/Error;"); + + /** + * {@code non-null;} the type + * {@code java.lang.IllegalMonitorStateException} + */ + public static final Type TYPE_IllegalMonitorStateException = + Type.intern("Ljava/lang/IllegalMonitorStateException;"); + + /** {@code non-null;} the type {@code java.lang.NegativeArraySizeException} */ + public static final Type TYPE_NegativeArraySizeException = + Type.intern("Ljava/lang/NegativeArraySizeException;"); + + /** {@code non-null;} the type {@code java.lang.NullPointerException} */ + public static final Type TYPE_NullPointerException = + Type.intern("Ljava/lang/NullPointerException;"); + + /** {@code non-null;} the list {@code [java.lang.Error]} */ + public static final StdTypeList LIST_Error = StdTypeList.make(TYPE_Error); + + /** + * {@code non-null;} the list {@code[java.lang.Error, + * java.lang.ArithmeticException]} + */ + public static final StdTypeList LIST_Error_ArithmeticException = + StdTypeList.make(TYPE_Error, TYPE_ArithmeticException); + + /** + * {@code non-null;} the list {@code[java.lang.Error, + * java.lang.ClassCastException]} + */ + public static final StdTypeList LIST_Error_ClassCastException = + StdTypeList.make(TYPE_Error, TYPE_ClassCastException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NegativeArraySizeException]} + */ + public static final StdTypeList LIST_Error_NegativeArraySizeException = + StdTypeList.make(TYPE_Error, TYPE_NegativeArraySizeException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException]} + */ + public static final StdTypeList LIST_Error_NullPointerException = + StdTypeList.make(TYPE_Error, TYPE_NullPointerException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException, + * java.lang.ArrayIndexOutOfBoundsException]} + */ + public static final StdTypeList LIST_Error_Null_ArrayIndexOutOfBounds = + StdTypeList.make(TYPE_Error, + TYPE_NullPointerException, + TYPE_ArrayIndexOutOfBoundsException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException, + * java.lang.ArrayIndexOutOfBoundsException, + * java.lang.ArrayStoreException]} + */ + public static final StdTypeList LIST_Error_Null_ArrayIndex_ArrayStore = + StdTypeList.make(TYPE_Error, + TYPE_NullPointerException, + TYPE_ArrayIndexOutOfBoundsException, + TYPE_ArrayStoreException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException, + * java.lang.IllegalMonitorStateException]} + */ + public static final StdTypeList + LIST_Error_Null_IllegalMonitorStateException = + StdTypeList.make(TYPE_Error, + TYPE_NullPointerException, + TYPE_IllegalMonitorStateException); + + /** + * This class is uninstantiable. + */ + private Exceptions() { + // This space intentionally left blank. + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/FillArrayDataInsn.java b/dexlib/src/main/java/com/android/dx/rop/code/FillArrayDataInsn.java new file mode 100644 index 000000000..1f233ec3c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/FillArrayDataInsn.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import java.util.ArrayList; + +/** + * Instruction which fills a newly created array with a predefined list of + * constant values. + */ +public final class FillArrayDataInsn + extends Insn { + + /** non-null: initial values to fill the newly created array */ + private final ArrayList initValues; + + /** + * non-null: type of the array. Will be used to determine the width of + * elements in the array-data table. + */ + private final Constant arrayType; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param initValues {@code non-null;} list of initial values to fill the array + * @param cst {@code non-null;} type of the new array + */ + public FillArrayDataInsn(Rop opcode, SourcePosition position, + RegisterSpecList sources, + ArrayList initValues, + Constant cst) { + super(opcode, position, null, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_NONE) { + throw new IllegalArgumentException("opcode with invalid branchingness: " + opcode.getBranchingness()); + } + + this.initValues = initValues; + this.arrayType = cst; + } + + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** + * Return the list of init values + * @return {@code non-null;} list of init values + */ + public ArrayList getInitValues() { + return initValues; + } + + /** + * Return the type of the newly created array + * @return {@code non-null;} array type + */ + public Constant getConstant() { + return arrayType; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitFillArrayDataInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new FillArrayDataInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + initValues, arrayType); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new FillArrayDataInsn(getOpcode(), getPosition(), + sources, initValues, arrayType); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/Insn.java b/dexlib/src/main/java/com/android/dx/rop/code/Insn.java new file mode 100644 index 000000000..322cbaed7 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/Insn.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.ToHuman; + +/** + * A register-based instruction. An instruction is the combination of + * an opcode (which specifies operation and source/result types), a + * list of actual sources and result registers/values, and additional + * information. + */ +public abstract class Insn implements ToHuman { + /** {@code non-null;} opcode */ + private final Rop opcode; + + /** {@code non-null;} source position */ + private final SourcePosition position; + + /** {@code null-ok;} spec for the result of this instruction, if any */ + private final RegisterSpec result; + + /** {@code non-null;} specs for all the sources of this instruction */ + private final RegisterSpecList sources; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + */ + public Insn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpecList sources) { + if (opcode == null) { + throw new NullPointerException("opcode == null"); + } + + if (position == null) { + throw new NullPointerException("position == null"); + } + + if (sources == null) { + throw new NullPointerException("sources == null"); + } + + this.opcode = opcode; + this.position = position; + this.result = result; + this.sources = sources; + } + + /** + * {@inheritDoc} + * + * Instances of this class compare by identity. That is, + * {@code x.equals(y)} is only true if {@code x == y}. + */ + @Override + public final boolean equals(Object other) { + return (this == other); + } + + /** + * {@inheritDoc} + * + * This implementation returns the identity hashcode of this + * instance. This is proper, since instances of this class compare + * by identity (see {@link #equals}). + */ + @Override + public final int hashCode() { + return System.identityHashCode(this); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return toStringWithInline(getInlineString()); + } + + /** + * Gets a human-oriented (and slightly lossy) string for this instance. + * + * @return {@code non-null;} the human string form + */ + public String toHuman() { + return toHumanWithInline(getInlineString()); + } + + /** + * Gets an "inline" string portion for toHuman(), if available. This + * is the portion that appears after the Rop opcode + * + * @return {@code null-ok;} if non-null, the inline text for toHuman() + */ + public String getInlineString() { + return null; + } + + /** + * Gets the opcode. + * + * @return {@code non-null;} the opcode + */ + public final Rop getOpcode() { + return opcode; + } + + /** + * Gets the source position. + * + * @return {@code non-null;} the source position + */ + public final SourcePosition getPosition() { + return position; + } + + /** + * Gets the result spec, if any. A return value of {@code null} + * means this instruction returns nothing. + * + * @return {@code null-ok;} the result spec, if any + */ + public final RegisterSpec getResult() { + return result; + } + + /** + * Gets the spec of a local variable assignment that occurs at this + * instruction, or null if no local variable assignment occurs. This + * may be the result register, or for {@code mark-local} insns + * it may be the source. + * + * @return {@code null-ok;} a named register spec or null + */ + public final RegisterSpec getLocalAssignment() { + RegisterSpec assignment; + if (opcode.getOpcode() == RegOps.MARK_LOCAL) { + assignment = sources.get(0); + } else { + assignment = result; + } + + if (assignment == null) { + return null; + } + + LocalItem localItem = assignment.getLocalItem(); + + if (localItem == null) { + return null; + } + + return assignment; + } + + /** + * Gets the source specs. + * + * @return {@code non-null;} the source specs + */ + public final RegisterSpecList getSources() { + return sources; + } + + /** + * Gets whether this instruction can possibly throw an exception. This + * is just a convenient wrapper for {@code getOpcode().canThrow()}. + * + * @return {@code true} iff this instruction can possibly throw + */ + public final boolean canThrow() { + return opcode.canThrow(); + } + + /** + * Gets the list of possibly-caught exceptions. This returns {@link + * StdTypeList#EMPTY} if this instruction has no handlers, + * which can be either if this instruction can't possibly + * throw or if it merely doesn't handle any of its possible + * exceptions. To determine whether this instruction can throw, + * use {@link #canThrow}. + * + * @return {@code non-null;} the catches list + */ + public abstract TypeList getCatches(); + + /** + * Calls the appropriate method on the given visitor, depending on the + * class of this instance. Subclasses must override this. + * + * @param visitor {@code non-null;} the visitor to call on + */ + public abstract void accept(Visitor visitor); + + /** + * Returns an instance that is just like this one, except that it + * has a catch list with the given item appended to the end. This + * method throws an exception if this instance can't possibly + * throw. To determine whether this instruction can throw, use + * {@link #canThrow}. + * + * @param type {@code non-null;} type to append to the catch list + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract Insn withAddedCatch(Type type); + + /** + * Returns an instance that is just like this one, except that all + * register references have been offset by the given delta. + * + * @param delta the amount to offset register references by + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract Insn withRegisterOffset(int delta); + + /** + * Returns an instance that is just like this one, except that, if + * possible, the insn is converted into a version in which a source + * (if it is a constant) is represented directly rather than as a + * register reference. {@code this} is returned in cases where the + * translation is not possible. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public Insn withSourceLiteral() { + return this; + } + + /** + * Returns an exact copy of this Insn + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public Insn copy() { + return withRegisterOffset(0); + } + + + /** + * Compares, handling nulls safely + * + * @param a first object + * @param b second object + * @return true if they're equal or both null. + */ + private static boolean equalsHandleNulls (Object a, Object b) { + return (a == b) || ((a != null) && a.equals(b)); + } + + /** + * Compares Insn contents, since {@code Insn.equals()} is defined + * to be an identity compare. Insn's are {@code contentEquals()} + * if they have the same opcode, registers, source position, and other + * metadata. + * + * @return true in the case described above + */ + public boolean contentEquals(Insn b) { + return opcode == b.getOpcode() + && position.equals(b.getPosition()) + && (getClass() == b.getClass()) + && equalsHandleNulls(result, b.getResult()) + && equalsHandleNulls(sources, b.getSources()) + && StdTypeList.equalContents(getCatches(), b.getCatches()); + } + + /** + * Returns an instance that is just like this one, except + * with new result and source registers. + * + * @param result {@code null-ok;} new result register + * @param sources {@code non-null;} new sources registers + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources); + + /** + * Returns the string form of this instance, with the given bit added in + * the standard location for an inline argument. + * + * @param extra {@code null-ok;} the inline argument string + * @return {@code non-null;} the string form + */ + protected final String toStringWithInline(String extra) { + StringBuffer sb = new StringBuffer(80); + + sb.append("Insn{"); + sb.append(position); + sb.append(' '); + sb.append(opcode); + + if (extra != null) { + sb.append(' '); + sb.append(extra); + } + + sb.append(" :: "); + + if (result != null) { + sb.append(result); + sb.append(" <- "); + } + + sb.append(sources); + sb.append('}'); + + return sb.toString(); + } + + /** + * Returns the human string form of this instance, with the given + * bit added in the standard location for an inline argument. + * + * @param extra {@code null-ok;} the inline argument string + * @return {@code non-null;} the human string form + */ + protected final String toHumanWithInline(String extra) { + StringBuffer sb = new StringBuffer(80); + + sb.append(position); + sb.append(": "); + sb.append(opcode.getNickname()); + + if (extra != null) { + sb.append("("); + sb.append(extra); + sb.append(")"); + } + + if (result == null) { + sb.append(" ."); + } else { + sb.append(" "); + sb.append(result.toHuman()); + } + + sb.append(" <-"); + + int sz = sources.size(); + if (sz == 0) { + sb.append(" ."); + } else { + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(sources.get(i).toHuman()); + } + } + + return sb.toString(); + } + + + /** + * Visitor interface for this (outer) class. + */ + public static interface Visitor { + /** + * Visits a {@link PlainInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitPlainInsn(PlainInsn insn); + + /** + * Visits a {@link PlainCstInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitPlainCstInsn(PlainCstInsn insn); + + /** + * Visits a {@link SwitchInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitSwitchInsn(SwitchInsn insn); + + /** + * Visits a {@link ThrowingCstInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitThrowingCstInsn(ThrowingCstInsn insn); + + /** + * Visits a {@link ThrowingInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitThrowingInsn(ThrowingInsn insn); + + /** + * Visits a {@link FillArrayDataInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn); + + /** + * Visits a {@link InvokePolymorphicInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitInvokePolymorphicInsn(InvokePolymorphicInsn insn); + } + + /** + * Base implementation of {@link Visitor}, which has empty method + * bodies for all methods. + */ + public static class BaseVisitor implements Visitor { + /** {@inheritDoc} */ + @Override + public void visitPlainInsn(PlainInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public void visitPlainCstInsn(PlainCstInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public void visitSwitchInsn(SwitchInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingInsn(ThrowingInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public void visitInvokePolymorphicInsn(InvokePolymorphicInsn insn) { + // This space intentionally left blank. + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/InsnList.java b/dexlib/src/main/java/com/android/dx/rop/code/InsnList.java new file mode 100644 index 000000000..88abd7256 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/InsnList.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.util.FixedSizeList; + +/** + * List of {@link Insn} instances. + */ +public final class InsnList + extends FixedSizeList { + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public InsnList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Insn get(int n) { + return (Insn) get0(n); + } + + /** + * Sets the instruction at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param insn {@code non-null;} the instruction to set at {@code n} + */ + public void set(int n, Insn insn) { + set0(n, insn); + } + + /** + * Gets the last instruction. This is just a convenient shorthand for + * {@code get(size() - 1)}. + * + * @return {@code non-null;} the last instruction + */ + public Insn getLast() { + return get(size() - 1); + } + + /** + * Visits each instruction in the list, in order. + * + * @param visitor {@code non-null;} visitor to use + */ + public void forEach(Insn.Visitor visitor) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + get(i).accept(visitor); + } + } + + /** + * Compares the contents of this {@code InsnList} with another. + * The blocks must have the same number of insns, and each Insn must + * also return true to {@code Insn.contentEquals()}. + * + * @param b to compare + * @return true in the case described above. + */ + public boolean contentEquals(InsnList b) { + if (b == null) return false; + + int sz = size(); + + if (sz != b.size()) return false; + + for (int i = 0; i < sz; i++) { + if (!get(i).contentEquals(b.get(i))) { + return false; + } + } + + return true; + } + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. Mutability of the result is inherited from the + * original. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public InsnList withRegisterOffset(int delta) { + int sz = size(); + InsnList result = new InsnList(sz); + + for (int i = 0; i < sz; i++) { + Insn one = (Insn) get0(i); + if (one != null) { + result.set0(i, one.withRegisterOffset(delta)); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/InvokePolymorphicInsn.java b/dexlib/src/main/java/com/android/dx/rop/code/InvokePolymorphicInsn.java new file mode 100644 index 000000000..5033aed72 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/InvokePolymorphicInsn.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstProtoRef; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; + +/** + * An invoke-polymorphic instruction. This is a throwing instruction with + * multiple constants. + */ +public class InvokePolymorphicInsn extends Insn { + private static final CstString INVOKE_DESCRIPTOR = + new CstString("([Ljava/lang/Object;)Ljava/lang/Object;"); + + /** {@code non-null;} list of exceptions caught */ + private final TypeList catches; + + /** + * {@code non-null;} method as it appears at the call site of the original + * invoke-virtual instruction. This is used to construct the invoke method + * to target and the call-site prototype. + */ + private final CstMethodRef callSiteMethod; + + /** + * {@code non-null;} method to invoke, either {@code java.lang.invoke.MethodHandle.invoke} or + * {@code java.lang.invoke.MethodHandle.invokeExact}. + */ + private final CstMethodRef invokeMethod; + + /** + * {@code non-null;} the call site prototype. + */ + private final CstProtoRef callSiteProto; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param catches {@code non-null;} list of exceptions caught + * @param callSiteMethod {@code non-null;} the method called by + * invoke-virtual that this instance will replace. + */ + public InvokePolymorphicInsn(Rop opcode, SourcePosition position, RegisterSpecList sources, TypeList catches, + CstMethodRef callSiteMethod) { + super(opcode, position, null, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_THROW) { + throw new IllegalArgumentException("opcode with invalid branchingness: " + opcode.getBranchingness()); + } + + if (catches == null) { + throw new NullPointerException("catches == null"); + } + this.catches = catches; + + if (callSiteMethod == null) { + throw new NullPointerException("callSiteMethod == null"); + } else if (!callSiteMethod.isSignaturePolymorphic()) { + throw new IllegalArgumentException("callSiteMethod is not signature polymorphic"); + } + + this.callSiteMethod = callSiteMethod; + this.invokeMethod = makeInvokeMethod(callSiteMethod); + this.callSiteProto = makeCallSiteProto(callSiteMethod); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return this.catches; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitInvokePolymorphicInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + return new InvokePolymorphicInsn(getOpcode(), getPosition(), + getSources(), catches.withAddedType(type), getCallSiteMethod()); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new InvokePolymorphicInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + catches, getCallSiteMethod()); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, RegisterSpecList sources) { + return new InvokePolymorphicInsn(getOpcode(), getPosition(), + sources, catches, getCallSiteMethod()); + } + + /** + * Gets the method as it appears at the call site of the original + * invoke-virtual instruction. + * + * @return {@code non-null;} the original method reference + */ + public CstMethodRef getCallSiteMethod() { + return callSiteMethod; + } + + /** + * Gets the method to be invoked. This will be will either be + * {@code java.lang.invoke.MethodHandle.invoke()} or + * {@code java.lang.invoke.MethodHandle.invokeExact()}. + * + * @return {@code non-null;} method reference to be invoked + */ + public CstMethodRef getInvokeMethod() { + return invokeMethod; + } + + /** + * Gets the call site prototype. The call site prototype is provided + * as an argument to invoke-polymorphic to enable type checking and + * type conversion. + * + * @return {@code non-null;} Prototype reference for call site + */ + public CstProtoRef getCallSiteProto() { + return callSiteProto; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return getInvokeMethod().toString() + " " + + getCallSiteProto().toString() + " " + + ThrowingInsn.toCatchString(catches); + } + + private static CstMethodRef makeInvokeMethod(final CstMethodRef callSiteMethod) { + // The name is either invoke or invokeExact. The INVOKE_DESCRIPTOR is fixed. + CstNat cstNat = new CstNat(callSiteMethod.getNat().getName(), INVOKE_DESCRIPTOR); + return new CstMethodRef(CstType.METHOD_HANDLE, cstNat); + } + + private static CstProtoRef makeCallSiteProto(final CstMethodRef callSiteMethod) { + return new CstProtoRef(callSiteMethod.getPrototype(true)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/LocalItem.java b/dexlib/src/main/java/com/android/dx/rop/code/LocalItem.java new file mode 100644 index 000000000..d238f216b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/LocalItem.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.CstString; + +/** + * A local variable item: either a name or a signature or both. + */ +public class LocalItem implements Comparable { + /** {@code null-ok;} local variable name */ + private final CstString name; + + /** {@code null-ok;} local variable signature */ + private final CstString signature; + + /** + * Make a new item. If both name and signature are null, null is returned. + * + * TODO: intern these + * + * @param name {@code null-ok;} local variable name + * @param signature {@code null-ok;} local variable signature + * @return {@code non-null;} appropriate instance. + */ + public static LocalItem make(CstString name, CstString signature) { + if (name == null && signature == null) { + return null; + } + + return new LocalItem (name, signature); + } + + /** + * Constructs instance. + * + * @param name {@code null-ok;} local variable name + * @param signature {@code null-ok;} local variable signature + */ + private LocalItem(CstString name, CstString signature) { + this.name = name; + this.signature = signature; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof LocalItem)) { + return false; + } + + LocalItem local = (LocalItem) other; + + return 0 == compareTo(local); + } + + /** + * Compares two strings like String.compareTo(), excepts treats a null + * as the least-possible string value. + * + * @return negative integer, zero, or positive integer in accordance + * with Comparable.compareTo() + */ + private static int compareHandlesNulls(CstString a, CstString b) { + if (a == b) { + return 0; + } else if (a == null) { + return -1; + } else if (b == null) { + return 1; + } else { + return a.compareTo(b); + } + } + + /** {@inheritDoc} */ + public int compareTo(LocalItem local) { + int ret; + + ret = compareHandlesNulls(name, local.name); + + if (ret != 0) { + return ret; + } + + ret = compareHandlesNulls(signature, local.signature); + + return ret; + } + + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (name == null ? 0 : name.hashCode()) * 31 + + (signature == null ? 0 : signature.hashCode()); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + if (name != null && signature == null) { + return name.toQuoted(); + } else if (name == null && signature == null) { + return ""; + } + + return "[" + (name == null ? "" : name.toQuoted()) + + "|" + (signature == null ? "" : signature.toQuoted()); + } + + /** + * Gets name. + * + * @return {@code null-ok;} name + */ + public CstString getName() { + return name; + } + + /** + * Gets signature. + * + * @return {@code null-ok;} signature + */ + public CstString getSignature() { + return signature; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/LocalVariableExtractor.java b/dexlib/src/main/java/com/android/dx/rop/code/LocalVariableExtractor.java new file mode 100644 index 000000000..c2c40216d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/LocalVariableExtractor.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.util.Bits; +import com.android.dx.util.IntList; + +/** + * Code to figure out which local variables are active at which points in + * a method. + */ +public final class LocalVariableExtractor { + /** {@code non-null;} method being extracted from */ + private final RopMethod method; + + /** {@code non-null;} block list for the method */ + private final BasicBlockList blocks; + + /** {@code non-null;} result in-progress */ + private final LocalVariableInfo resultInfo; + + /** {@code non-null;} work set indicating blocks needing to be processed */ + private final int[] workSet; + + /** + * Extracts out all the local variable information from the given method. + * + * @param method {@code non-null;} the method to extract from + * @return {@code non-null;} the extracted information + */ + public static LocalVariableInfo extract(RopMethod method) { + LocalVariableExtractor lve = new LocalVariableExtractor(method); + return lve.doit(); + } + + /** + * Constructs an instance. This method is private. Use {@link #extract}. + * + * @param method {@code non-null;} the method to extract from + */ + private LocalVariableExtractor(RopMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + BasicBlockList blocks = method.getBlocks(); + int maxLabel = blocks.getMaxLabel(); + + this.method = method; + this.blocks = blocks; + this.resultInfo = new LocalVariableInfo(method); + this.workSet = Bits.makeBitSet(maxLabel); + } + + /** + * Does the extraction. + * + * @return {@code non-null;} the extracted information + */ + private LocalVariableInfo doit() { + for (int label = method.getFirstLabel(); + label >= 0; + label = Bits.findFirst(workSet, 0)) { + Bits.clear(workSet, label); + processBlock(label); + } + + resultInfo.setImmutable(); + return resultInfo; + } + + /** + * Processes a single block. + * + * @param label {@code >= 0;} label of the block to process + */ + private void processBlock(int label) { + RegisterSpecSet primaryState = resultInfo.mutableCopyOfStarts(label); + BasicBlock block = blocks.labelToBlock(label); + InsnList insns = block.getInsns(); + int insnSz = insns.size(); + + /* + * We may have to treat the last instruction specially: If it + * can (but doesn't always) throw, and the exception can be + * caught within the same method, then we need to use the + * state *before* executing it to be what is merged into + * exception targets. + */ + boolean canThrowDuringLastInsn = block.hasExceptionHandlers() && + (insns.getLast().getResult() != null); + int freezeSecondaryStateAt = insnSz - 1; + RegisterSpecSet secondaryState = primaryState; + + /* + * Iterate over the instructions, adding information for each place + * that the active variable set changes. + */ + + for (int i = 0; i < insnSz; i++) { + if (canThrowDuringLastInsn && (i == freezeSecondaryStateAt)) { + // Until this point, primaryState == secondaryState. + primaryState.setImmutable(); + primaryState = primaryState.mutableCopy(); + } + + Insn insn = insns.get(i); + RegisterSpec result; + + result = insn.getLocalAssignment(); + + if (result == null) { + /* + * If an assignment assigns over an existing local, make + * sure to mark the local as going out of scope. + */ + + result = insn.getResult(); + + if (result != null + && primaryState.get(result.getReg()) != null) { + primaryState.remove(primaryState.get(result.getReg())); + } + continue; + } + + result = result.withSimpleType(); + + RegisterSpec already = primaryState.get(result); + /* + * The equals() check ensures we only add new info if + * the instruction causes a change to the set of + * active variables. + */ + if (!result.equals(already)) { + /* + * If this insn represents a local moving from one register + * to another, remove the association between the old register + * and the local. + */ + RegisterSpec previous + = primaryState.localItemToSpec(result.getLocalItem()); + + if (previous != null + && (previous.getReg() != result.getReg())) { + + primaryState.remove(previous); + } + + resultInfo.addAssignment(insn, result); + primaryState.put(result); + } + } + + primaryState.setImmutable(); + + /* + * Merge this state into the start state for each successor, + * and update the work set where required (that is, in cases + * where the start state for a block changes). + */ + + IntList successors = block.getSuccessors(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessor(); + + for (int i = 0; i < succSz; i++) { + int succ = successors.get(i); + RegisterSpecSet state = (succ == primarySuccessor) ? + primaryState : secondaryState; + + if (resultInfo.mergeStarts(succ, state)) { + Bits.set(workSet, succ); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/LocalVariableInfo.java b/dexlib/src/main/java/com/android/dx/rop/code/LocalVariableInfo.java new file mode 100644 index 000000000..4376a2e6b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/LocalVariableInfo.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.MutabilityControl; +import java.util.HashMap; + +/** + * Container for local variable information for a particular {@link + * RopMethod}. + */ +public final class LocalVariableInfo + extends MutabilityControl { + /** {@code >= 0;} the register count for the method */ + private final int regCount; + + /** + * {@code non-null;} {@link RegisterSpecSet} to use when indicating a block + * that has no locals; it is empty and immutable but has an appropriate + * max size for the method + */ + private final RegisterSpecSet emptySet; + + /** + * {@code non-null;} array consisting of register sets representing the + * sets of variables already assigned upon entry to each block, + * where array indices correspond to block labels + */ + private final RegisterSpecSet[] blockStarts; + + /** {@code non-null;} map from instructions to the variable each assigns */ + private final HashMap insnAssignments; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method being represented by this instance + */ + public LocalVariableInfo(RopMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + BasicBlockList blocks = method.getBlocks(); + int maxLabel = blocks.getMaxLabel(); + + this.regCount = blocks.getRegCount(); + this.emptySet = new RegisterSpecSet(regCount); + this.blockStarts = new RegisterSpecSet[maxLabel]; + this.insnAssignments = + new HashMap(blocks.getInstructionCount()); + + emptySet.setImmutable(); + } + + /** + * Sets the register set associated with the start of the block with + * the given label. + * + * @param label {@code >= 0;} the block label + * @param specs {@code non-null;} the register set to associate with the block + */ + public void setStarts(int label, RegisterSpecSet specs) { + throwIfImmutable(); + + if (specs == null) { + throw new NullPointerException("specs == null"); + } + + try { + blockStarts[label] = specs; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus label"); + } + } + + /** + * Merges the given register set into the set for the block with the + * given label. If there was not already an associated set, then this + * is the same as calling {@link #setStarts}. Otherwise, this will + * merge the two sets and call {@link #setStarts} on the result of the + * merge. + * + * @param label {@code >= 0;} the block label + * @param specs {@code non-null;} the register set to merge into the start set + * for the block + * @return {@code true} if the merge resulted in an actual change + * to the associated set (including storing one for the first time) or + * {@code false} if there was no change + */ + public boolean mergeStarts(int label, RegisterSpecSet specs) { + RegisterSpecSet start = getStarts0(label); + boolean changed = false; + + if (start == null) { + setStarts(label, specs); + return true; + } + + RegisterSpecSet newStart = start.mutableCopy(); + if (start.size() != 0) { + newStart.intersect(specs, true); + } else { + newStart = specs.mutableCopy(); + } + + if (start.equals(newStart)) { + return false; + } + + newStart.setImmutable(); + setStarts(label, newStart); + + return true; + } + + /** + * Gets the register set associated with the start of the block + * with the given label. This returns an empty set with the appropriate + * max size if no set was associated with the block in question. + * + * @param label {@code >= 0;} the block label + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(int label) { + RegisterSpecSet result = getStarts0(label); + + return (result != null) ? result : emptySet; + } + + /** + * Gets the register set associated with the start of the given + * block. This is just convenient shorthand for + * {@code getStarts(block.getLabel())}. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(BasicBlock block) { + return getStarts(block.getLabel()); + } + + /** + * Gets a mutable copy of the register set associated with the + * start of the block with the given label. This returns a + * newly-allocated empty {@link RegisterSpecSet} of appropriate + * max size if there is not yet any set associated with the block. + * + * @param label {@code >= 0;} the block label + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet mutableCopyOfStarts(int label) { + RegisterSpecSet result = getStarts0(label); + + return (result != null) ? + result.mutableCopy() : new RegisterSpecSet(regCount); + } + + /** + * Adds an assignment association for the given instruction and + * register spec. This throws an exception if the instruction + * doesn't actually perform a named variable assignment. + * + * Note: Although the instruction contains its own spec for + * the result, it still needs to be passed in explicitly to this + * method, since the spec that is stored here should always have a + * simple type and the one in the instruction can be an arbitrary + * {@link TypeBearer} (such as a constant value). + * + * @param insn {@code non-null;} the instruction in question + * @param spec {@code non-null;} the associated register spec + */ + public void addAssignment(Insn insn, RegisterSpec spec) { + throwIfImmutable(); + + if (insn == null) { + throw new NullPointerException("insn == null"); + } + + if (spec == null) { + throw new NullPointerException("spec == null"); + } + + insnAssignments.put(insn, spec); + } + + /** + * Gets the named register being assigned by the given instruction, if + * previously stored in this instance. + * + * @param insn {@code non-null;} instruction in question + * @return {@code null-ok;} the named register being assigned, if any + */ + public RegisterSpec getAssignment(Insn insn) { + return insnAssignments.get(insn); + } + + /** + * Gets the number of assignments recorded by this instance. + * + * @return {@code >= 0;} the number of assignments + */ + public int getAssignmentCount() { + return insnAssignments.size(); + } + + public void debugDump() { + for (int label = 0 ; label < blockStarts.length; label++) { + if (blockStarts[label] == null) { + continue; + } + + if (blockStarts[label] == emptySet) { + System.out.printf("%04x: empty set\n", label); + } else { + System.out.printf("%04x: %s\n", label, blockStarts[label]); + } + } + } + + /** + * Helper method, to get the starts for a label, throwing the + * right exception for range problems. + * + * @param label {@code >= 0;} the block label + * @return {@code null-ok;} associated register set or {@code null} if there + * is none + */ + private RegisterSpecSet getStarts0(int label) { + try { + return blockStarts[label]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus label"); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/PlainCstInsn.java b/dexlib/src/main/java/com/android/dx/rop/code/PlainCstInsn.java new file mode 100644 index 000000000..021abd631 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/PlainCstInsn.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; + +/** + * Instruction which contains an explicit reference to a constant + * but which cannot throw an exception. + */ +public final class PlainCstInsn + extends CstInsn { + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + * @param cst {@code non-null;} the constant + */ + public PlainCstInsn(Rop opcode, SourcePosition position, + RegisterSpec result, RegisterSpecList sources, + Constant cst) { + super(opcode, position, result, sources, cst); + + if (opcode.getBranchingness() != Rop.BRANCH_NONE) { + throw new IllegalArgumentException("opcode with invalid branchingness: " + opcode.getBranchingness()); + } + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitPlainCstInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new PlainCstInsn(getOpcode(), getPosition(), + getResult().withOffset(delta), + getSources().withOffset(delta), + getConstant()); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new PlainCstInsn(getOpcode(), getPosition(), + result, + sources, + getConstant()); + + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/PlainInsn.java b/dexlib/src/main/java/com/android/dx/rop/code/PlainInsn.java new file mode 100644 index 000000000..241ab11ff --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/PlainInsn.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.rop.type.TypeList; + +/** + * Plain instruction, which has no embedded data and which cannot possibly + * throw an exception. + */ +public final class PlainInsn + extends Insn { + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + */ + public PlainInsn(Rop opcode, SourcePosition position, + RegisterSpec result, RegisterSpecList sources) { + super(opcode, position, result, sources); + + switch (opcode.getBranchingness()) { + case Rop.BRANCH_SWITCH: + case Rop.BRANCH_THROW: { + throw new IllegalArgumentException("opcode with invalid branchingness: " + opcode.getBranchingness()); + } + } + + if (result != null && opcode.getBranchingness() != Rop.BRANCH_NONE) { + // move-result-pseudo is required here + throw new IllegalArgumentException + ("can't mix branchingness with result"); + } + } + + /** + * Constructs a single-source instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param source {@code non-null;} spec for the source + */ + public PlainInsn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpec source) { + this(opcode, position, result, RegisterSpecList.make(source)); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitPlainInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new PlainInsn(getOpcode(), getPosition(), + getResult().withOffset(delta), + getSources().withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public Insn withSourceLiteral() { + RegisterSpecList sources = getSources(); + int szSources = sources.size(); + + if (szSources == 0) { + return this; + } + + TypeBearer lastType = sources.get(szSources - 1).getTypeBearer(); + + if (!lastType.isConstant()) { + // Check for reverse subtraction, where first source is constant + TypeBearer firstType = sources.get(0).getTypeBearer(); + if (szSources == 2 && firstType.isConstant()) { + Constant cst = (Constant) firstType; + RegisterSpecList newSources = sources.withoutFirst(); + Rop newRop = Rops.ropFor(getOpcode().getOpcode(), getResult(), + newSources, cst); + return new PlainCstInsn(newRop, getPosition(), getResult(), + newSources, cst); + } + return this; + } else { + + Constant cst = (Constant) lastType; + + RegisterSpecList newSources = sources.withoutLast(); + + Rop newRop; + try { + // Check for constant subtraction and flip it to be addition + int opcode = getOpcode().getOpcode(); + if (opcode == RegOps.SUB && cst instanceof CstInteger) { + opcode = RegOps.ADD; + cst = CstInteger.make(-((CstInteger)cst).getValue()); + } + newRop = Rops.ropFor(opcode, getResult(), newSources, cst); + } catch (IllegalArgumentException ex) { + // There's no rop for this case + return this; + } + + return new PlainCstInsn(newRop, getPosition(), + getResult(), newSources, cst); + } + } + + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new PlainInsn(getOpcode(), getPosition(), + result, + sources); + + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/RegOps.java b/dexlib/src/main/java/com/android/dx/rop/code/RegOps.java new file mode 100644 index 000000000..6b51f0888 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/RegOps.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.util.Hex; + +/** + * All the register-based opcodes, and related utilities. + * + *

Note: Opcode descriptions use a rough pseudocode. {@code r} + * is the result register, {@code x} is the first argument, + * {@code y} is the second argument, and {@code z} is the + * third argument. The expression which describes + * the operation uses Java-ish syntax but is preceded by type indicators for + * each of the values. + */ +public final class RegOps { + /** {@code nop()} */ + public static final int NOP = 1; + + /** {@code T: any type; r,x: T :: r = x;} */ + public static final int MOVE = 2; + + /** {@code T: any type; r,param(x): T :: r = param(x)} */ + public static final int MOVE_PARAM = 3; + + /** + * {@code T: Throwable; r: T :: r = caught_exception}. + * Note: This opcode should only ever be used in the + * first instruction of a block, and such blocks must be + * the start of an exception handler. + */ + public static final int MOVE_EXCEPTION = 4; + + /** {@code T: any type; r, literal: T :: r = literal;} */ + public static final int CONST = 5; + + /** {@code goto label} */ + public static final int GOTO = 6; + + /** + * {@code T: int or Object; x,y: T :: if (x == y) goto + * label} + */ + public static final int IF_EQ = 7; + + /** + * {@code T: int or Object; x,y: T :: if (x != y) goto + * label} + */ + public static final int IF_NE = 8; + + /** {@code x,y: int :: if (x < y) goto label} */ + public static final int IF_LT = 9; + + /** {@code x,y: int :: if (x >= y) goto label} */ + public static final int IF_GE = 10; + + /** {@code x,y: int :: if (x <= y) goto label} */ + public static final int IF_LE = 11; + + /** {@code x,y: int :: if (x > y) goto label} */ + public static final int IF_GT = 12; + + /** {@code x: int :: goto table[x]} */ + public static final int SWITCH = 13; + + /** {@code T: any numeric type; r,x,y: T :: r = x + y} */ + public static final int ADD = 14; + + /** {@code T: any numeric type; r,x,y: T :: r = x - y} */ + public static final int SUB = 15; + + /** {@code T: any numeric type; r,x,y: T :: r = x * y} */ + public static final int MUL = 16; + + /** {@code T: any numeric type; r,x,y: T :: r = x / y} */ + public static final int DIV = 17; + + /** + * {@code T: any numeric type; r,x,y: T :: r = x % y} + * (Java-style remainder) + */ + public static final int REM = 18; + + /** {@code T: any numeric type; r,x: T :: r = -x} */ + public static final int NEG = 19; + + /** {@code T: any integral type; r,x,y: T :: r = x & y} */ + public static final int AND = 20; + + /** {@code T: any integral type; r,x,y: T :: r = x | y} */ + public static final int OR = 21; + + /** {@code T: any integral type; r,x,y: T :: r = x ^ y} */ + public static final int XOR = 22; + + /** + * {@code T: any integral type; r,x: T; y: int :: r = x << y} + */ + public static final int SHL = 23; + + /** + * {@code T: any integral type; r,x: T; y: int :: r = x >> y} + * (signed right-shift) + */ + public static final int SHR = 24; + + /** + * {@code T: any integral type; r,x: T; y: int :: r = x >>> y} + * (unsigned right-shift) + */ + public static final int USHR = 25; + + /** {@code T: any integral type; r,x: T :: r = ~x} */ + public static final int NOT = 26; + + /** + * {@code T: any numeric type; r: int; x,y: T :: r = (x == y) ? 0 + * : (x > y) ? 1 : -1} (Java-style "cmpl" where a NaN is + * considered "less than" all other values; also used for integral + * comparisons) + */ + public static final int CMPL = 27; + + /** + * {@code T: any floating point type; r: int; x,y: T :: r = (x == y) ? 0 + * : (x < y) ? -1 : 1} (Java-style "cmpg" where a NaN is + * considered "greater than" all other values) + */ + public static final int CMPG = 28; + + /** + * {@code T: any numeric type; U: any numeric type; r: T; x: U :: + * r = (T) x} (numeric type conversion between the four + * "real" numeric types) + */ + public static final int CONV = 29; + + /** + * {@code r,x: int :: r = (x << 24) >> 24} (Java-style + * convert int to byte) + */ + public static final int TO_BYTE = 30; + + /** + * {@code r,x: int :: r = x & 0xffff} (Java-style convert int to char) + */ + public static final int TO_CHAR = 31; + + /** + * {@code r,x: int :: r = (x << 16) >> 16} (Java-style + * convert int to short) + */ + public static final int TO_SHORT = 32; + + /** {@code T: return type for the method; x: T; return x} */ + public static final int RETURN = 33; + + /** {@code T: any type; r: int; x: T[]; :: r = x.length} */ + public static final int ARRAY_LENGTH = 34; + + /** {@code x: Throwable :: throw(x)} */ + public static final int THROW = 35; + + /** {@code x: Object :: monitorenter(x)} */ + public static final int MONITOR_ENTER = 36; + + /** {@code x: Object :: monitorexit(x)} */ + public static final int MONITOR_EXIT = 37; + + /** {@code T: any type; r: T; x: T[]; y: int :: r = x[y]} */ + public static final int AGET = 38; + + /** {@code T: any type; x: T; y: T[]; z: int :: x[y] = z} */ + public static final int APUT = 39; + + /** + * {@code T: any non-array object type :: r = + * alloc(T)} (allocate heap space for an object) + */ + public static final int NEW_INSTANCE = 40; + + /** {@code T: any array type; r: T; x: int :: r = new T[x]} */ + public static final int NEW_ARRAY = 41; + + /** + * {@code T: any array type; r: T; x: int; v0..vx: T :: r = new T[x] + * {v0, ..., vx}} + */ + public static final int FILLED_NEW_ARRAY = 42; + + /** + * {@code T: any object type; x: Object :: (T) x} (can + * throw {@code ClassCastException}) + */ + public static final int CHECK_CAST = 43; + + /** + * {@code T: any object type; x: Object :: x instanceof T} + */ + public static final int INSTANCE_OF = 44; + + /** + * {@code T: any type; r: T; x: Object; f: instance field spec of + * type T :: r = x.f} + */ + public static final int GET_FIELD = 45; + + /** + * {@code T: any type; r: T; f: static field spec of type T :: r = + * f} + */ + public static final int GET_STATIC = 46; + + /** + * {@code T: any type; x: T; y: Object; f: instance field spec of type + * T :: y.f = x} + */ + public static final int PUT_FIELD = 47; + + /** + * {@code T: any type; f: static field spec of type T; x: T :: f = x} + */ + public static final int PUT_STATIC = 48; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; m: static method spec; + * y0: T0; y1: T1 ... :: r = m(y0, y1, ...)} (call static + * method) + */ + public static final int INVOKE_STATIC = 49; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call normal + * virtual method) + */ + public static final int INVOKE_VIRTUAL = 50; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call + * superclass virtual method) + */ + public static final int INVOKE_SUPER = 51; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call + * direct/special method) + */ + public static final int INVOKE_DIRECT = 52; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: interface + * (instance) method spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, + * ...)} (call interface method) + */ + public static final int INVOKE_INTERFACE = 53; + + /** + * {@code T0: any type; name: local variable name :: mark(name,T0)} + * (mark beginning or end of local variable name) + */ + public static final int MARK_LOCAL = 54; + + /** + * {@code T: Any type; r: T :: r = return_type}. + * Note: This opcode should only ever be used in the + * first instruction of a block following an invoke-*. + */ + public static final int MOVE_RESULT = 55; + + /** + * {@code T: Any type; r: T :: r = return_type}. + * Note: This opcode should only ever be used in the + * first instruction of a block following a non-invoke throwing insn + */ + public static final int MOVE_RESULT_PSEUDO = 56; + + /** {@code T: Any primitive type; v0..vx: T :: {v0, ..., vx}} */ + public static final int FILL_ARRAY_DATA = 57; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: java.lang.invoke.MethodHandle; + * m: signature polymorphic method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call signature + * polymorphic method) + */ + public static final int INVOKE_POLYMORPHIC = 58; + + /** + * This class is uninstantiable. + */ + private RegOps() { + // This space intentionally left blank. + } + + /** + * Gets the name of the given opcode. + * + * @param opcode the opcode + * @return {@code non-null;} its name + */ + public static String opName(int opcode) { + switch (opcode) { + case NOP: return "nop"; + case MOVE: return "move"; + case MOVE_PARAM: return "move-param"; + case MOVE_EXCEPTION: return "move-exception"; + case CONST: return "const"; + case GOTO: return "goto"; + case IF_EQ: return "if-eq"; + case IF_NE: return "if-ne"; + case IF_LT: return "if-lt"; + case IF_GE: return "if-ge"; + case IF_LE: return "if-le"; + case IF_GT: return "if-gt"; + case SWITCH: return "switch"; + case ADD: return "add"; + case SUB: return "sub"; + case MUL: return "mul"; + case DIV: return "div"; + case REM: return "rem"; + case NEG: return "neg"; + case AND: return "and"; + case OR: return "or"; + case XOR: return "xor"; + case SHL: return "shl"; + case SHR: return "shr"; + case USHR: return "ushr"; + case NOT: return "not"; + case CMPL: return "cmpl"; + case CMPG: return "cmpg"; + case CONV: return "conv"; + case TO_BYTE: return "to-byte"; + case TO_CHAR: return "to-char"; + case TO_SHORT: return "to-short"; + case RETURN: return "return"; + case ARRAY_LENGTH: return "array-length"; + case THROW: return "throw"; + case MONITOR_ENTER: return "monitor-enter"; + case MONITOR_EXIT: return "monitor-exit"; + case AGET: return "aget"; + case APUT: return "aput"; + case NEW_INSTANCE: return "new-instance"; + case NEW_ARRAY: return "new-array"; + case FILLED_NEW_ARRAY: return "filled-new-array"; + case CHECK_CAST: return "check-cast"; + case INSTANCE_OF: return "instance-of"; + case GET_FIELD: return "get-field"; + case GET_STATIC: return "get-static"; + case PUT_FIELD: return "put-field"; + case PUT_STATIC: return "put-static"; + case INVOKE_STATIC: return "invoke-static"; + case INVOKE_VIRTUAL: return "invoke-virtual"; + case INVOKE_SUPER: return "invoke-super"; + case INVOKE_DIRECT: return "invoke-direct"; + case INVOKE_INTERFACE: return "invoke-interface"; + case MOVE_RESULT: return "move-result"; + case MOVE_RESULT_PSEUDO: return "move-result-pseudo"; + case FILL_ARRAY_DATA: return "fill-array-data"; + case INVOKE_POLYMORPHIC: return "invoke-polymorphic"; + } + + return "unknown-" + Hex.u1(opcode); + } + + /** + * Given an IF_* RegOp, returns the right-to-left flipped version. For + * example, IF_GT becomes IF_LT. + * + * @param opcode An IF_* RegOp + * @return flipped IF Regop + */ + public static int flippedIfOpcode(final int opcode) { + switch (opcode) { + case RegOps.IF_EQ: + case RegOps.IF_NE: + return opcode; + case RegOps.IF_LT: + return RegOps.IF_GT; + case RegOps.IF_GE: + return RegOps.IF_LE; + case RegOps.IF_LE: + return RegOps.IF_GE; + case RegOps.IF_GT: + return RegOps.IF_LT; + default: + throw new RuntimeException("Unrecognized IF regop: " + opcode); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/RegisterSpec.java b/dexlib/src/main/java/com/android/dx/rop/code/RegisterSpec.java new file mode 100644 index 000000000..c31478fa3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/RegisterSpec.java @@ -0,0 +1,674 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.command.dexer.Main; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.ToHuman; +import java.util.HashMap; + +/** + * Combination of a register number and a type, used as the sources and + * destinations of register-based operations. + */ +public final class RegisterSpec + implements TypeBearer, ToHuman, Comparable { + /** {@code non-null;} string to prefix register numbers with */ + public static final String PREFIX = "v"; + + /** {@code non-null;} intern table for instances */ + private static final HashMap theInterns = + new HashMap(1000); + + /** {@code non-null;} common comparison instance used while interning */ + private static final ForComparison theInterningItem = new ForComparison(); + + /** {@code >= 0;} register number */ + private final int reg; + + /** {@code non-null;} type loaded or stored */ + private final TypeBearer type; + + /** + * {@code null-ok;} local variable info associated with this register, + * if any + */ + private final LocalItem local; + + /** + * Intern the given triple as an instance of this class. + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code null-ok;} the associated local variable, if any + * @return {@code non-null;} an appropriately-constructed instance + */ + private static RegisterSpec intern(int reg, TypeBearer type, + LocalItem local) { + synchronized (theInterns) { + theInterningItem.set(reg, type, local); + RegisterSpec found = theInterns.get(theInterningItem); + + if (found != null) { + return found; + } + + found = theInterningItem.toRegisterSpec(); + theInterns.put(found, found); + return found; + } + } + + /** + * Returns an instance for the given register number and type, with + * no variable info. This method is allowed to return shared + * instances (but doesn't necessarily do so). + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpec make(int reg, TypeBearer type) { + return intern(reg, type, null); + } + + /** + * Returns an instance for the given register number, type, and + * variable info. This method is allowed to return shared + * instances (but doesn't necessarily do so). + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code non-null;} the associated local variable + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpec make(int reg, TypeBearer type, + LocalItem local) { + if (local == null) { + throw new NullPointerException("local == null"); + } + + return intern(reg, type, local); + } + + /** + * Returns an instance for the given register number, type, and + * variable info. This method is allowed to return shared + * instances (but doesn't necessarily do so). + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code null-ok;} the associated variable info or null for + * none + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpec makeLocalOptional( + int reg, TypeBearer type, LocalItem local) { + + return intern(reg, type, local); + } + + /** + * Gets the string form for the given register number. + * + * @param reg {@code >= 0;} the register number + * @return {@code non-null;} the string form + */ + public static String regString(int reg) { + return PREFIX + reg; + } + + /** + * Constructs an instance. This constructor is private. Use + * {@link #make}. + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code null-ok;} the associated local variable, if any + */ + private RegisterSpec(int reg, TypeBearer type, LocalItem local) { + if (reg < 0) { + throw new IllegalArgumentException("reg < 0"); + } + + if (type == null) { + throw new NullPointerException("type == null"); + } + + this.reg = reg; + this.type = type; + this.local = local; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RegisterSpec)) { + if (other instanceof ForComparison) { + ForComparison fc = (ForComparison) other; + return equals(fc.reg, fc.type, fc.local); + } + return false; + } + + RegisterSpec spec = (RegisterSpec) other; + return equals(spec.reg, spec.type, spec.local); + } + + /** + * Like {@code equals}, but only consider the simple types of the + * registers. That is, this compares {@code getType()} on the types + * to ignore whatever arbitrary extra stuff might be carried around + * by an outer {@link TypeBearer}. + * + * @param other {@code null-ok;} spec to compare to + * @return {@code true} iff {@code this} and {@code other} are equal + * in the stated way + */ + public boolean equalsUsingSimpleType(RegisterSpec other) { + if (!matchesVariable(other)) { + return false; + } + + return (reg == other.reg); + } + + /** + * Like {@link #equalsUsingSimpleType} but ignoring the register number. + * This is useful to determine if two instances refer to the "same" + * local variable. + * + * @param other {@code null-ok;} spec to compare to + * @return {@code true} iff {@code this} and {@code other} are equal + * in the stated way + */ + public boolean matchesVariable(RegisterSpec other) { + if (other == null) { + return false; + } + + return type.getType().equals(other.type.getType()) + && ((local == other.local) + || ((local != null) && local.equals(other.local))); + } + + /** + * Helper for {@link #equals} and {@link #ForComparison.equals}, + * which actually does the test. + * + * @param reg value of the instance variable, for another instance + * @param type value of the instance variable, for another instance + * @param local value of the instance variable, for another instance + * @return whether this instance is equal to one with the given + * values + */ + private boolean equals(int reg, TypeBearer type, LocalItem local) { + return (this.reg == reg) + && this.type.equals(type) + && ((this.local == local) + || ((this.local != null) && this.local.equals(local))); + } + + /** + * Compares by (in priority order) register number, unwrapped type + * (that is types not {@link TypeBearer}s, and local info. + * + * @param other {@code non-null;} spec to compare to + * @return {@code -1..1;} standard result of comparison + */ + @Override + public int compareTo(RegisterSpec other) { + if (this.reg < other.reg) { + return -1; + } else if (this.reg > other.reg) { + return 1; + } + + int compare = type.getType().compareTo(other.type.getType()); + + if (compare != 0) { + return compare; + } + + if (this.local == null) { + return (other.local == null) ? 0 : -1; + } else if (other.local == null) { + return 1; + } + + return this.local.compareTo(other.local); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return hashCodeOf(reg, type, local); + } + + /** + * Helper for {@link #hashCode} and {@link #ForComparison.hashCode}, + * which actually does the calculation. + * + * @param reg value of the instance variable + * @param type value of the instance variable + * @param local value of the instance variable + * @return the hash code + */ + private static int hashCodeOf(int reg, TypeBearer type, LocalItem local) { + int hash = (local != null) ? local.hashCode() : 0; + + hash = (hash * 31 + type.hashCode()) * 31 + reg; + return hash; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return toString0(false); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return toString0(true); + } + + /** {@inheritDoc} */ + @Override + public Type getType() { + return type.getType(); + } + + /** {@inheritDoc} */ + @Override + public TypeBearer getFrameType() { + return type.getFrameType(); + } + + /** {@inheritDoc} */ + @Override + public final int getBasicType() { + return type.getBasicType(); + } + + /** {@inheritDoc} */ + @Override + public final int getBasicFrameType() { + return type.getBasicFrameType(); + } + + /** {@inheritDoc} */ + @Override + public final boolean isConstant() { + return false; + } + + /** + * Gets the register number. + * + * @return {@code >= 0;} the register number + */ + public int getReg() { + return reg; + } + + /** + * Gets the type (or actual value) which is loaded from or stored + * to the register associated with this instance. + * + * @return {@code non-null;} the type + */ + public TypeBearer getTypeBearer() { + return type; + } + + /** + * Gets the variable info associated with this instance, if any. + * + * @return {@code null-ok;} the variable info, or {@code null} if this + * instance has none + */ + public LocalItem getLocalItem() { + return local; + } + + /** + * Gets the next available register number after the one in this + * instance. This is equal to the register number plus the width + * (category) of the type used. Among other things, this may also + * be used to determine the minimum required register count + * implied by this instance. + * + * @return {@code >= 0;} the required registers size + */ + public int getNextReg() { + return reg + getCategory(); + } + + /** + * Gets the category of this instance's type. This is just a convenient + * shorthand for {@code getType().getCategory()}. + * + * @see #isCategory1 + * @see #isCategory2 + * @return {@code 1..2;} the category of this instance's type + */ + public int getCategory() { + return type.getType().getCategory(); + } + + /** + * Gets whether this instance's type is category 1. This is just a + * convenient shorthand for {@code getType().isCategory1()}. + * + * @see #getCategory + * @see #isCategory2 + * @return whether or not this instance's type is of category 1 + */ + public boolean isCategory1() { + return type.getType().isCategory1(); + } + + /** + * Gets whether this instance's type is category 2. This is just a + * convenient shorthand for {@code getType().isCategory2()}. + * + * @see #getCategory + * @see #isCategory1 + * @return whether or not this instance's type is of category 2 + */ + public boolean isCategory2() { + return type.getType().isCategory2(); + } + + /** + * Gets the string form for just the register number of this instance. + * + * @return {@code non-null;} the register string form + */ + public String regString() { + return regString(reg); + } + + /** + * Returns an instance that is the intersection between this instance + * and the given one, if any. The intersection is defined as follows: + * + *

    + *
  • If {@code other} is {@code null}, then the result + * is {@code null}. + *
  • If the register numbers don't match, then the intersection + * is {@code null}. Otherwise, the register number of the + * intersection is the same as the one in the two instances.
  • + *
  • If the types returned by {@code getType()} are not + * {@code equals()}, then the intersection is null.
  • + *
  • If the type bearers returned by {@code getTypeBearer()} + * are {@code equals()}, then the intersection's type bearer + * is the one from this instance. Otherwise, the intersection's + * type bearer is the {@code getType()} of this instance.
  • + *
  • If the locals are {@code equals()}, then the local info + * of the intersection is the local info of this instance. Otherwise, + * the local info of the intersection is {@code null}.
  • + *
+ * + * @param other {@code null-ok;} instance to intersect with (or {@code null}) + * @param localPrimary whether local variables are primary to the + * intersection; if {@code true}, then the only non-null + * results occur when registers being intersected have equal local + * infos (or both have {@code null} local infos) + * @return {@code null-ok;} the intersection + */ + public RegisterSpec intersect(RegisterSpec other, boolean localPrimary) { + if (this == other) { + // Easy out. + return this; + } + + if ((other == null) || (reg != other.getReg())) { + return null; + } + + LocalItem resultLocal = + ((local == null) || !local.equals(other.getLocalItem())) + ? null : local; + boolean sameName = (resultLocal == local); + + if (localPrimary && !sameName) { + return null; + } + + Type thisType = getType(); + Type otherType = other.getType(); + + // Note: Types are always interned. + if (thisType != otherType) { + return null; + } + + TypeBearer resultTypeBearer = + type.equals(other.getTypeBearer()) ? type : thisType; + + if ((resultTypeBearer == type) && sameName) { + // It turns out that the intersection is "this" after all. + return this; + } + + return (resultLocal == null) ? make(reg, resultTypeBearer) : + make(reg, resultTypeBearer, resultLocal); + } + + /** + * Returns an instance that is identical to this one, except that the + * register number is replaced by the given one. + * + * @param newReg {@code >= 0;} the new register number + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withReg(int newReg) { + if (reg == newReg) { + return this; + } + + return makeLocalOptional(newReg, type, local); + } + + /** + * Returns an instance that is identical to this one, except that + * the type is replaced by the given one. + * + * @param newType {@code non-null;} the new type + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withType(TypeBearer newType) { + return makeLocalOptional(reg, newType, local); + } + + /** + * Returns an instance that is identical to this one, except that the + * register number is offset by the given amount. + * + * @param delta the amount to offset the register number by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withOffset(int delta) { + if (delta == 0) { + return this; + } + + return withReg(reg + delta); + } + + /** + * Returns an instance that is identical to this one, except that + * the type bearer is replaced by the actual underlying type + * (thereby stripping off non-type information) with any + * initialization information stripped away as well. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withSimpleType() { + TypeBearer orig = type; + Type newType; + + if (orig instanceof Type) { + newType = (Type) orig; + } else { + newType = orig.getType(); + } + + if (newType.isUninitialized()) { + newType = newType.getInitializedType(); + } + + if (newType == orig) { + return this; + } + + return makeLocalOptional(reg, newType, local); + } + + /** + * Returns an instance that is identical to this one except that the + * local variable is as specified in the parameter. + * + * @param local {@code null-ok;} the local item or null for none + * @return an appropriate instance + */ + public RegisterSpec withLocalItem(LocalItem local) { + if ((this.local== local) + || ((this.local != null) && this.local.equals(local))) { + + return this; + } + + return makeLocalOptional(reg, type, local); + } + + /** + * @return boolean specifying if this instance is an even register or not. + */ + public boolean isEvenRegister() { + return ((getReg() & 1) == 0); + } + + /** + * Helper for {@link #toString} and {@link #toHuman}. + * + * @param human whether to be human-oriented + * @return {@code non-null;} the string form + */ + private String toString0(boolean human) { + StringBuffer sb = new StringBuffer(40); + + sb.append(regString()); + sb.append(":"); + + if (local != null) { + sb.append(local.toString()); + } + + Type justType = type.getType(); + sb.append(justType); + + if (justType != type) { + sb.append("="); + if (human && (type instanceof CstString)) { + sb.append(((CstString) type).toQuoted()); + } else if (human && (type instanceof Constant)) { + sb.append(type.toHuman()); + } else { + sb.append(type); + } + } + + return sb.toString(); + } + + public static void clearInternTable() { + theInterns.clear(); + } + + /** + * Holder of register spec data for the purposes of comparison (so that + * {@code RegisterSpec} itself can still keep {@code final} + * instance variables. + */ + private static class ForComparison { + /** {@code >= 0;} register number */ + private int reg; + + /** {@code non-null;} type loaded or stored */ + private TypeBearer type; + + /** + * {@code null-ok;} local variable associated with this + * register, if any + */ + private LocalItem local; + + /** + * Set all the instance variables. + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual + * value) which is loaded from or stored to the indicated + * register + * @param local {@code null-ok;} the associated local variable, if any + * @return {@code non-null;} an appropriately-constructed instance + */ + public void set(int reg, TypeBearer type, LocalItem local) { + this.reg = reg; + this.type = type; + this.local = local; + } + + /** + * Construct a {@code RegisterSpec} of this instance's + * contents. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec toRegisterSpec() { + return new RegisterSpec(reg, type, local); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RegisterSpec)) { + return false; + } + + RegisterSpec spec = (RegisterSpec) other; + return spec.equals(reg, type, local); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return hashCodeOf(reg, type, local); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/RegisterSpecList.java b/dexlib/src/main/java/com/android/dx/rop/code/RegisterSpecList.java new file mode 100644 index 000000000..c9680e7be --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/RegisterSpecList.java @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.FixedSizeList; +import java.util.BitSet; + +/** + * List of {@link RegisterSpec} instances. + */ +public final class RegisterSpecList + extends FixedSizeList implements TypeList { + /** {@code non-null;} no-element instance */ + public static final RegisterSpecList EMPTY = new RegisterSpecList(0); + + /** + * Makes a single-element instance. + * + * @param spec {@code non-null;} the element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec) { + RegisterSpecList result = new RegisterSpecList(1); + result.set(0, spec); + return result; + } + + /** + * Makes a two-element instance. + * + * @param spec0 {@code non-null;} the first element + * @param spec1 {@code non-null;} the second element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec0, + RegisterSpec spec1) { + RegisterSpecList result = new RegisterSpecList(2); + result.set(0, spec0); + result.set(1, spec1); + return result; + } + + /** + * Makes a three-element instance. + * + * @param spec0 {@code non-null;} the first element + * @param spec1 {@code non-null;} the second element + * @param spec2 {@code non-null;} the third element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1, + RegisterSpec spec2) { + RegisterSpecList result = new RegisterSpecList(3); + result.set(0, spec0); + result.set(1, spec1); + result.set(2, spec2); + return result; + } + + /** + * Makes a four-element instance. + * + * @param spec0 {@code non-null;} the first element + * @param spec1 {@code non-null;} the second element + * @param spec2 {@code non-null;} the third element + * @param spec3 {@code non-null;} the fourth element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1, + RegisterSpec spec2, + RegisterSpec spec3) { + RegisterSpecList result = new RegisterSpecList(4); + result.set(0, spec0); + result.set(1, spec1); + result.set(2, spec2); + result.set(3, spec3); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public RegisterSpecList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Type getType(int n) { + return get(n).getType().getType(); + } + + /** {@inheritDoc} */ + public int getWordCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + result += getType(i).getCategory(); + } + + return result; + } + + /** {@inheritDoc} */ + public TypeList withAddedType(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public RegisterSpec get(int n) { + return (RegisterSpec) get0(n); + } + + /** + * Returns a RegisterSpec in this list that uses the specified register, + * or null if there is none in this list. + * @param reg Register to find + * @return RegisterSpec that uses argument or null. + */ + public RegisterSpec specForRegister(int reg) { + int sz = size(); + for (int i = 0; i < sz; i++) { + RegisterSpec rs; + + rs = get(i); + + if (rs.getReg() == reg) { + return rs; + } + } + + return null; + } + + /** + * Returns the index of a RegisterSpec in this list that uses the specified + * register, or -1 if none in this list uses the register. + * @param reg Register to find + * @return index of RegisterSpec or -1 + */ + public int indexOfRegister(int reg) { + int sz = size(); + for (int i = 0; i < sz; i++) { + RegisterSpec rs; + + rs = get(i); + + if (rs.getReg() == reg) { + return i; + } + } + + return -1; + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param spec {@code non-null;} the value to store + */ + public void set(int n, RegisterSpec spec) { + set0(n, spec); + } + + /** + * Gets the minimum required register count implied by this + * instance. This is equal to the highest register number referred + * to plus the widest width (largest category) of the type used in + * that register. + * + * @return {@code >= 0;} the required registers size + */ + public int getRegistersSize() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec spec = (RegisterSpec) get0(i); + if (spec != null) { + int min = spec.getNextReg(); + if (min > result) { + result = min; + } + } + } + + return result; + } + + /** + * Returns a new instance, which is the same as this instance, + * except that it has an additional element prepended to the original. + * Mutability of the result is inherited from the original. + * + * @param spec {@code non-null;} the new first spec (to prepend) + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withFirst(RegisterSpec spec) { + int sz = size(); + RegisterSpecList result = new RegisterSpecList(sz + 1); + + for (int i = 0; i < sz; i++) { + result.set0(i + 1, get0(i)); + } + + result.set0(0, spec); + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a new instance, which is the same as this instance, + * except that its first element is removed. Mutability of the + * result is inherited from the original. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withoutFirst() { + int newSize = size() - 1; + + if (newSize == 0) { + return EMPTY; + } + + RegisterSpecList result = new RegisterSpecList(newSize); + + for (int i = 0; i < newSize; i++) { + result.set0(i, get0(i + 1)); + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a new instance, which is the same as this instance, + * except that its last element is removed. Mutability of the + * result is inherited from the original. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withoutLast() { + int newSize = size() - 1; + + if (newSize == 0) { + return EMPTY; + } + + RegisterSpecList result = new RegisterSpecList(newSize); + + for (int i = 0; i < newSize; i++) { + result.set0(i, get0(i)); + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a new instance, which contains a subset of the elements + * specified by the given BitSet. Indexes in the BitSet with a zero + * are included, while indexes with a one are excluded. Mutability + * of the result is inherited from the original. + * + * @param exclusionSet {@code non-null;} set of registers to exclude + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList subset(BitSet exclusionSet) { + int newSize = size() - exclusionSet.cardinality(); + + if (newSize == 0) { + return EMPTY; + } + + RegisterSpecList result = new RegisterSpecList(newSize); + + int newIndex = 0; + for (int oldIndex = 0; oldIndex < size(); oldIndex++) { + if (!exclusionSet.get(oldIndex)) { + result.set0(newIndex, get0(oldIndex)); + newIndex++; + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns an instance that is identical to this one, except that + * all register numbers are offset by the given amount. Mutability + * of the result is inherited from the original. + * + * @param delta the amount to offset the register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withOffset(int delta) { + int sz = size(); + + if (sz == 0) { + // Don't bother making a new zero-element instance. + return this; + } + + RegisterSpecList result = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + RegisterSpec one = (RegisterSpec) get0(i); + if (one != null) { + result.set0(i, one.withOffset(delta)); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns an instance that is identical to this one, except that + * all incompatible register numbers are renumbered sequentially from + * the given base, with the first number duplicated if indicated. If + * a null BitSet is given, it indicates all registers are incompatible. + * + * @param base the base register number + * @param duplicateFirst whether to duplicate the first number + * @param compatRegs {@code null-ok;} either a {@code non-null} set of + * compatible registers, or {@code null} to indicate all registers are + * incompatible + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withExpandedRegisters(int base, + boolean duplicateFirst, + BitSet compatRegs) { + int sz = size(); + + if (sz == 0) { + // Don't bother making a new zero-element instance. + return this; + } + + Expander expander = new Expander(this, compatRegs, base, duplicateFirst); + + for (int regIdx = 0; regIdx < sz; regIdx++) { + expander.expandRegister(regIdx); + } + + return expander.getResult(); + } + + private static class Expander { + private BitSet compatRegs; + private RegisterSpecList regSpecList; + private int base; + private RegisterSpecList result; + private boolean duplicateFirst; + + private Expander(RegisterSpecList regSpecList, BitSet compatRegs, int base, + boolean duplicateFirst) { + this.regSpecList = regSpecList; + this.compatRegs = compatRegs; + this.base = base; + this.result = new RegisterSpecList(regSpecList.size()); + this.duplicateFirst = duplicateFirst; + } + + private void expandRegister(int regIdx) { + expandRegister(regIdx, (RegisterSpec) regSpecList.get0(regIdx)); + } + + private void expandRegister(int regIdx, RegisterSpec registerToExpand) { + boolean replace = (compatRegs == null) ? true : !compatRegs.get(regIdx); + RegisterSpec expandedReg; + + if (replace) { + expandedReg = registerToExpand.withReg(base); + if (!duplicateFirst) { + base += expandedReg.getCategory(); + } + } else { + expandedReg = registerToExpand; + } + + // Reset duplicateFirst when the first register has been dealt with. + duplicateFirst = false; + + result.set0(regIdx, expandedReg); + } + + private RegisterSpecList getResult() { + if (regSpecList.isImmutable()) { + result.setImmutable(); + } + + return result; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/RegisterSpecSet.java b/dexlib/src/main/java/com/android/dx/rop/code/RegisterSpecSet.java new file mode 100644 index 000000000..14ef778d1 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/RegisterSpecSet.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.util.MutabilityControl; + +/** + * Set of {@link RegisterSpec} instances, where a given register number + * may appear only once in the set. + */ +public final class RegisterSpecSet + extends MutabilityControl { + /** {@code non-null;} no-element instance */ + public static final RegisterSpecSet EMPTY = new RegisterSpecSet(0); + + /** + * {@code non-null;} array of register specs, where each element is + * {@code null} or is an instance whose {@code reg} + * matches the array index + */ + private final RegisterSpec[] specs; + + /** {@code >= -1;} size of the set or {@code -1} if not yet calculated */ + private int size; + + /** + * Constructs an instance. The instance is initially empty. + * + * @param maxSize {@code >= 0;} the maximum register number (exclusive) that + * may be represented in this instance + */ + public RegisterSpecSet(int maxSize) { + super(maxSize != 0); + + this.specs = new RegisterSpec[maxSize]; + this.size = 0; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RegisterSpecSet)) { + return false; + } + + RegisterSpecSet otherSet = (RegisterSpecSet) other; + RegisterSpec[] otherSpecs = otherSet.specs; + int len = specs.length; + + if ((len != otherSpecs.length) || (size() != otherSet.size())) { + return false; + } + + for (int i = 0; i < len; i++) { + RegisterSpec s1 = specs[i]; + RegisterSpec s2 = otherSpecs[i]; + + if (s1 == s2) { + continue; + } + + if ((s1 == null) || !s1.equals(s2)) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int len = specs.length; + int hash = 0; + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + int oneHash = (spec == null) ? 0 : spec.hashCode(); + hash = (hash * 31) + oneHash; + } + + return hash; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int len = specs.length; + StringBuffer sb = new StringBuffer(len * 25); + + sb.append('{'); + + boolean any = false; + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + if (spec != null) { + if (any) { + sb.append(", "); + } else { + any = true; + } + sb.append(spec); + } + } + + sb.append('}'); + return sb.toString(); + } + + /** + * Gets the maximum number of registers that may be in this instance, which + * is also the maximum-plus-one of register numbers that may be + * represented. + * + * @return {@code >= 0;} the maximum size + */ + public int getMaxSize() { + return specs.length; + } + + /** + * Gets the current size of this instance. + * + * @return {@code >= 0;} the size + */ + public int size() { + int result = size; + + if (result < 0) { + int len = specs.length; + + result = 0; + for (int i = 0; i < len; i++) { + if (specs[i] != null) { + result++; + } + } + + size = result; + } + + return result; + } + + /** + * Gets the element with the given register number, if any. + * + * @param reg {@code >= 0;} the desired register number + * @return {@code null-ok;} the element with the given register number or + * {@code null} if there is none + */ + public RegisterSpec get(int reg) { + try { + return specs[reg]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus reg"); + } + } + + /** + * Gets the element with the same register number as the given + * spec, if any. This is just a convenient shorthand for + * {@code get(spec.getReg())}. + * + * @param spec {@code non-null;} spec with the desired register number + * @return {@code null-ok;} the element with the matching register number or + * {@code null} if there is none + */ + public RegisterSpec get(RegisterSpec spec) { + return get(spec.getReg()); + } + + /** + * Returns the spec in this set that's currently associated with a + * given local (type, name, and signature), or {@code null} if there is + * none. This ignores the register number of the given spec but + * matches on everything else. + * + * @param spec {@code non-null;} local to look for + * @return {@code null-ok;} first register found that matches, if any + */ + public RegisterSpec findMatchingLocal(RegisterSpec spec) { + int length = specs.length; + + for (int reg = 0; reg < length; reg++) { + RegisterSpec s = specs[reg]; + + if (s == null) { + continue; + } + + if (spec.matchesVariable(s)) { + return s; + } + } + + return null; + } + + /** + * Returns the spec in this set that's currently associated with a given + * local (name and signature), or {@code null} if there is none. + * + * @param local {@code non-null;} local item to search for + * @return {@code null-ok;} first register found with matching name and signature + */ + public RegisterSpec localItemToSpec(LocalItem local) { + int length = specs.length; + + for (int reg = 0; reg < length; reg++) { + RegisterSpec spec = specs[reg]; + + if ((spec != null) && local.equals(spec.getLocalItem())) { + return spec; + } + } + + return null; + } + + /** + * Removes a spec from the set. Only the register number + * of the parameter is significant. + * + * @param toRemove {@code non-null;} register to remove. + */ + public void remove(RegisterSpec toRemove) { + try { + specs[toRemove.getReg()] = null; + size = -1; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus reg"); + } + } + + /** + * Puts the given spec into the set. If there is already an element in + * the set with the same register number, it is replaced. Additionally, + * if the previous element is for a category-2 register, then that + * previous element is nullified. Finally, if the given spec is for + * a category-2 register, then the immediately subsequent element + * is nullified. + * + * @param spec {@code non-null;} the register spec to put in the instance + */ + public void put(RegisterSpec spec) { + throwIfImmutable(); + + if (spec == null) { + throw new NullPointerException("spec == null"); + } + + size = -1; + + try { + int reg = spec.getReg(); + specs[reg] = spec; + + if (reg > 0) { + int prevReg = reg - 1; + RegisterSpec prevSpec = specs[prevReg]; + if ((prevSpec != null) && (prevSpec.getCategory() == 2)) { + specs[prevReg] = null; + } + } + + if (spec.getCategory() == 2) { + specs[reg + 1] = null; + } + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("spec.getReg() out of range"); + } + } + + /** + * Put the entire contents of the given set into this one. + * + * @param set {@code non-null;} the set to put into this instance + */ + public void putAll(RegisterSpecSet set) { + int max = set.getMaxSize(); + + for (int i = 0; i < max; i++) { + RegisterSpec spec = set.get(i); + if (spec != null) { + put(spec); + } + } + } + + /** + * Intersects this instance with the given one, modifying this + * instance. The intersection consists of the pairwise + * {@link RegisterSpec#intersect} of corresponding elements from + * this instance and the given one where both are non-null. + * + * @param other {@code non-null;} set to intersect with + * @param localPrimary whether local variables are primary to + * the intersection; if {@code true}, then the only non-null + * result elements occur when registers being intersected have + * equal names (or both have {@code null} names) + */ + public void intersect(RegisterSpecSet other, boolean localPrimary) { + throwIfImmutable(); + + RegisterSpec[] otherSpecs = other.specs; + int thisLen = specs.length; + int len = Math.min(thisLen, otherSpecs.length); + + size = -1; + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + + if (spec == null) { + continue; + } + + RegisterSpec intersection = + spec.intersect(otherSpecs[i], localPrimary); + if (intersection != spec) { + specs[i] = intersection; + } + } + + for (int i = len; i < thisLen; i++) { + specs[i] = null; + } + } + + /** + * Returns an instance that is identical to this one, except that + * all register numbers are offset by the given amount. Mutability + * of the result is inherited from the original. + * + * @param delta the amount to offset the register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecSet withOffset(int delta) { + int len = specs.length; + RegisterSpecSet result = new RegisterSpecSet(len + delta); + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + if (spec != null) { + result.put(spec.withOffset(delta)); + } + } + + result.size = size; + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Makes and return a mutable copy of this instance. + * + * @return {@code non-null;} the mutable copy + */ + public RegisterSpecSet mutableCopy() { + int len = specs.length; + RegisterSpecSet copy = new RegisterSpecSet(len); + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + if (spec != null) { + copy.put(spec); + } + } + + copy.size = size; + + return copy; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/Rop.java b/dexlib/src/main/java/com/android/dx/rop/code/Rop.java new file mode 100644 index 000000000..02e33284d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/Rop.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.Hex; + +/** + * Class that describes all the immutable parts of register-based operations. + */ +public final class Rop { + /** minimum {@code BRANCH_*} value */ + public static final int BRANCH_MIN = 1; + + /** indicates a non-branching op */ + public static final int BRANCH_NONE = 1; + + /** indicates a function/method return */ + public static final int BRANCH_RETURN = 2; + + /** indicates an unconditional goto */ + public static final int BRANCH_GOTO = 3; + + /** indicates a two-way branch */ + public static final int BRANCH_IF = 4; + + /** indicates a switch-style branch */ + public static final int BRANCH_SWITCH = 5; + + /** indicates a throw-style branch (both always-throws and may-throw) */ + public static final int BRANCH_THROW = 6; + + /** maximum {@code BRANCH_*} value */ + public static final int BRANCH_MAX = 6; + + /** the opcode; one of the constants in {@link RegOps} */ + private final int opcode; + + /** + * {@code non-null;} result type of this operation; {@link Type#VOID} for + * no-result operations + */ + private final Type result; + + /** {@code non-null;} types of all the sources of this operation */ + private final TypeList sources; + + /** {@code non-null;} list of possible types thrown by this operation */ + private final TypeList exceptions; + + /** + * the branchingness of this op; one of the {@code BRANCH_*} + * constants in this class + */ + private final int branchingness; + + /** whether this is a function/method call op or similar */ + private final boolean isCallLike; + + /** {@code null-ok;} nickname, if specified (used for debugging) */ + private final String nickname; + + /** + * Constructs an instance. This method is private. Use one of the + * public constructors. + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + * @param branchingness the branchingness of this op; one of the + * {@code BRANCH_*} constants + * @param isCallLike whether the op is a function/method call or similar + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, + TypeList exceptions, int branchingness, boolean isCallLike, + String nickname) { + if (result == null) { + throw new NullPointerException("result == null"); + } + + if (sources == null) { + throw new NullPointerException("sources == null"); + } + + if (exceptions == null) { + throw new NullPointerException("exceptions == null"); + } + + if ((branchingness < BRANCH_MIN) || (branchingness > BRANCH_MAX)) { + throw new IllegalArgumentException("invalid branchingness: " + branchingness); + } + + if ((exceptions.size() != 0) && (branchingness != BRANCH_THROW)) { + throw new IllegalArgumentException("exceptions / branchingness " + + "mismatch"); + } + + this.opcode = opcode; + this.result = result; + this.sources = sources; + this.exceptions = exceptions; + this.branchingness = branchingness; + this.isCallLike = isCallLike; + this.nickname = nickname; + } + + /** + * Constructs an instance. The constructed instance is never a + * call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + * @param branchingness the branchingness of this op; one of the + * {@code BRANCH_*} constants + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, + TypeList exceptions, int branchingness, String nickname) { + this(opcode, result, sources, exceptions, branchingness, false, + nickname); + } + + /** + * Constructs a no-exception instance. The constructed instance is never a + * call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param branchingness the branchingness of this op; one of the + * {@code BRANCH_*} constants + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, int branchingness, + String nickname) { + this(opcode, result, sources, StdTypeList.EMPTY, branchingness, false, + nickname); + } + + /** + * Constructs a non-branching no-exception instance. The + * {@code branchingness} is always {@code BRANCH_NONE}, + * and it is never a call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, String nickname) { + this(opcode, result, sources, StdTypeList.EMPTY, Rop.BRANCH_NONE, + false, nickname); + } + + /** + * Constructs a non-empty exceptions instance. Its + * {@code branchingness} is always {@code BRANCH_THROW}, + * but it is never a call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, TypeList exceptions, + String nickname) { + this(opcode, result, sources, exceptions, Rop.BRANCH_THROW, false, + nickname); + } + + /** + * Constructs a non-nicknamed instance with non-empty exceptions, which + * is always a call-like op (see {@link #isCallLike}). Its + * {@code branchingness} is always {@code BRANCH_THROW}. + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + */ + public Rop(int opcode, TypeList sources, TypeList exceptions) { + this(opcode, Type.VOID, sources, exceptions, Rop.BRANCH_THROW, true, + null); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + // Easy out. + return true; + } + + if (!(other instanceof Rop)) { + return false; + } + + Rop rop = (Rop) other; + + return (opcode == rop.opcode) && + (branchingness == rop.branchingness) && + (result == rop.result) && + sources.equals(rop.sources) && + exceptions.equals(rop.exceptions); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int h = (opcode * 31) + branchingness; + h = (h * 31) + result.hashCode(); + h = (h * 31) + sources.hashCode(); + h = (h * 31) + exceptions.hashCode(); + + return h; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(40); + + sb.append("Rop{"); + + sb.append(RegOps.opName(opcode)); + + if (result != Type.VOID) { + sb.append(" "); + sb.append(result); + } else { + sb.append(" ."); + } + + sb.append(" <-"); + + int sz = sources.size(); + if (sz == 0) { + sb.append(" ."); + } else { + for (int i = 0; i < sz; i++) { + sb.append(' '); + sb.append(sources.getType(i)); + } + } + + if (isCallLike) { + sb.append(" call"); + } + + sz = exceptions.size(); + if (sz != 0) { + sb.append(" throws"); + for (int i = 0; i < sz; i++) { + sb.append(' '); + Type one = exceptions.getType(i); + if (one == Type.THROWABLE) { + sb.append(""); + } else { + sb.append(exceptions.getType(i)); + } + } + } else { + switch (branchingness) { + case BRANCH_NONE: sb.append(" flows"); break; + case BRANCH_RETURN: sb.append(" returns"); break; + case BRANCH_GOTO: sb.append(" gotos"); break; + case BRANCH_IF: sb.append(" ifs"); break; + case BRANCH_SWITCH: sb.append(" switches"); break; + default: sb.append(" " + Hex.u1(branchingness)); break; + } + } + + sb.append('}'); + + return sb.toString(); + } + + /** + * Gets the opcode. + * + * @return the opcode + */ + public int getOpcode() { + return opcode; + } + + /** + * Gets the result type. A return value of {@link Type#VOID} + * means this operation returns nothing. + * + * @return {@code null-ok;} the result spec + */ + public Type getResult() { + return result; + } + + /** + * Gets the source types. + * + * @return {@code non-null;} the source types + */ + public TypeList getSources() { + return sources; + } + + /** + * Gets the list of exception types that might be thrown. + * + * @return {@code non-null;} the list of exception types + */ + public TypeList getExceptions() { + return exceptions; + } + + /** + * Gets the branchingness of this instance. + * + * @return the branchingness + */ + public int getBranchingness() { + return branchingness; + } + + /** + * Gets whether this opcode is a function/method call or similar. + * + * @return {@code true} iff this opcode is call-like + */ + public boolean isCallLike() { + return isCallLike; + } + + + /** + * Gets whether this opcode is commutative (the order of its sources are + * unimportant) or not. All commutative Rops have exactly two sources and + * have no branchiness. + * + * @return true if rop is commutative + */ + public boolean isCommutative() { + switch (opcode) { + case RegOps.AND: + case RegOps.OR: + case RegOps.XOR: + case RegOps.ADD: + case RegOps.MUL: + return true; + default: + return false; + } + } + + /** + * Gets the nickname. If this instance has no nickname, this returns + * the result of calling {@link #toString}. + * + * @return {@code non-null;} the nickname + */ + public String getNickname() { + if (nickname != null) { + return nickname; + } + + return toString(); + } + + /** + * Gets whether this operation can possibly throw an exception. This + * is just a convenient wrapper for + * {@code getExceptions().size() != 0}. + * + * @return {@code true} iff this operation can possibly throw + */ + public final boolean canThrow() { + return (exceptions.size() != 0); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/RopMethod.java b/dexlib/src/main/java/com/android/dx/rop/code/RopMethod.java new file mode 100644 index 000000000..f1643c18a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/RopMethod.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.util.Hex; +import com.android.dx.util.IntList; + +/** + * All of the parts that make up a method at the rop layer. + */ +public final class RopMethod { + /** {@code non-null;} basic block list of the method */ + private final BasicBlockList blocks; + + /** {@code >= 0;} label for the block which starts the method */ + private final int firstLabel; + + /** + * {@code null-ok;} array of predecessors for each block, indexed by block + * label + */ + private IntList[] predecessors; + + /** + * {@code null-ok;} the predecessors for the implicit "exit" block, that is + * the labels for the blocks that return, if calculated + */ + private IntList exitPredecessors; + + /** + * Constructs an instance. + * + * @param blocks {@code non-null;} basic block list of the method + * @param firstLabel {@code >= 0;} the label of the first block to execute + */ + public RopMethod(BasicBlockList blocks, int firstLabel) { + if (blocks == null) { + throw new NullPointerException("blocks == null"); + } + + if (firstLabel < 0) { + throw new IllegalArgumentException("firstLabel < 0"); + } + + this.blocks = blocks; + this.firstLabel = firstLabel; + + this.predecessors = null; + this.exitPredecessors = null; + } + + /** + * Gets the basic block list for this method. + * + * @return {@code non-null;} the list + */ + public BasicBlockList getBlocks() { + return blocks; + } + + /** + * Gets the label for the first block in the method that this list + * represents. + * + * @return {@code >= 0;} the first-block label + */ + public int getFirstLabel() { + return firstLabel; + } + + /** + * Gets the predecessors associated with the given block. This throws + * an exception if there is no block with the given label. + * + * @param label {@code >= 0;} the label of the block in question + * @return {@code non-null;} the predecessors of that block + */ + public IntList labelToPredecessors(int label) { + if (exitPredecessors == null) { + calcPredecessors(); + } + + IntList result = predecessors[label]; + + if (result == null) { + throw new RuntimeException("no such block: " + Hex.u2(label)); + } + + return result; + } + + /** + * Gets the exit predecessors for this instance. + * + * @return {@code non-null;} the exit predecessors + */ + public IntList getExitPredecessors() { + if (exitPredecessors == null) { + calcPredecessors(); + } + + return exitPredecessors; + } + + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RopMethod withRegisterOffset(int delta) { + RopMethod result = new RopMethod(blocks.withRegisterOffset(delta), + firstLabel); + + if (exitPredecessors != null) { + /* + * The predecessors have been calculated. It's safe to + * inject these into the new instance, since the + * transformation being applied doesn't affect the + * predecessors. + */ + result.exitPredecessors = exitPredecessors; + result.predecessors = predecessors; + } + + return result; + } + + /** + * Calculates the predecessor sets for each block as well as for the + * exit. + */ + private void calcPredecessors() { + int maxLabel = blocks.getMaxLabel(); + IntList[] predecessors = new IntList[maxLabel]; + IntList exitPredecessors = new IntList(10); + int sz = blocks.size(); + + /* + * For each block, find its successors, and add the block's label to + * the successor's predecessors. + */ + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + int label = one.getLabel(); + IntList successors = one.getSuccessors(); + int ssz = successors.size(); + if (ssz == 0) { + // This block exits. + exitPredecessors.add(label); + } else { + for (int j = 0; j < ssz; j++) { + int succLabel = successors.get(j); + IntList succPreds = predecessors[succLabel]; + if (succPreds == null) { + succPreds = new IntList(10); + predecessors[succLabel] = succPreds; + } + succPreds.add(label); + } + } + } + + // Sort and immutablize all the predecessor lists. + for (int i = 0; i < maxLabel; i++) { + IntList preds = predecessors[i]; + if (preds != null) { + preds.sort(); + preds.setImmutable(); + } + } + + exitPredecessors.sort(); + exitPredecessors.setImmutable(); + + /* + * The start label might not ever have had any predecessors + * added to it (probably doesn't, because of how Java gets + * translated into rop form). So, check for this and rectify + * the situation if required. + */ + if (predecessors[firstLabel] == null) { + predecessors[firstLabel] = IntList.EMPTY; + } + + this.predecessors = predecessors; + this.exitPredecessors = exitPredecessors; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/Rops.java b/dexlib/src/main/java/com/android/dx/rop/code/Rops.java new file mode 100644 index 000000000..dfe4619c9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/Rops.java @@ -0,0 +1,2111 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstBaseMethodRef; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.rop.type.TypeList; + +/** + * Standard instances of {@link Rop}. + */ +public final class Rops { + /** {@code nop()} */ + public static final Rop NOP = + new Rop(RegOps.NOP, Type.VOID, StdTypeList.EMPTY, "nop"); + + /** {@code r,x: int :: r = x;} */ + public static final Rop MOVE_INT = + new Rop(RegOps.MOVE, Type.INT, StdTypeList.INT, "move-int"); + + /** {@code r,x: long :: r = x;} */ + public static final Rop MOVE_LONG = + new Rop(RegOps.MOVE, Type.LONG, StdTypeList.LONG, "move-long"); + + /** {@code r,x: float :: r = x;} */ + public static final Rop MOVE_FLOAT = + new Rop(RegOps.MOVE, Type.FLOAT, StdTypeList.FLOAT, "move-float"); + + /** {@code r,x: double :: r = x;} */ + public static final Rop MOVE_DOUBLE = + new Rop(RegOps.MOVE, Type.DOUBLE, StdTypeList.DOUBLE, "move-double"); + + /** {@code r,x: Object :: r = x;} */ + public static final Rop MOVE_OBJECT = + new Rop(RegOps.MOVE, Type.OBJECT, StdTypeList.OBJECT, "move-object"); + + /** + * {@code r,x: ReturnAddress :: r = x;} + * + * Note that this rop-form instruction has no dex-form equivilent and + * must be removed before the dex conversion. + */ + public static final Rop MOVE_RETURN_ADDRESS = + new Rop(RegOps.MOVE, Type.RETURN_ADDRESS, + StdTypeList.RETURN_ADDRESS, "move-return-address"); + + /** {@code r,param(x): int :: r = param(x);} */ + public static final Rop MOVE_PARAM_INT = + new Rop(RegOps.MOVE_PARAM, Type.INT, StdTypeList.EMPTY, + "move-param-int"); + + /** {@code r,param(x): long :: r = param(x);} */ + public static final Rop MOVE_PARAM_LONG = + new Rop(RegOps.MOVE_PARAM, Type.LONG, StdTypeList.EMPTY, + "move-param-long"); + + /** {@code r,param(x): float :: r = param(x);} */ + public static final Rop MOVE_PARAM_FLOAT = + new Rop(RegOps.MOVE_PARAM, Type.FLOAT, StdTypeList.EMPTY, + "move-param-float"); + + /** {@code r,param(x): double :: r = param(x);} */ + public static final Rop MOVE_PARAM_DOUBLE = + new Rop(RegOps.MOVE_PARAM, Type.DOUBLE, StdTypeList.EMPTY, + "move-param-double"); + + /** {@code r,param(x): Object :: r = param(x);} */ + public static final Rop MOVE_PARAM_OBJECT = + new Rop(RegOps.MOVE_PARAM, Type.OBJECT, StdTypeList.EMPTY, + "move-param-object"); + + /** {@code r, literal: int :: r = literal;} */ + public static final Rop CONST_INT = + new Rop(RegOps.CONST, Type.INT, StdTypeList.EMPTY, "const-int"); + + /** {@code r, literal: long :: r = literal;} */ + public static final Rop CONST_LONG = + new Rop(RegOps.CONST, Type.LONG, StdTypeList.EMPTY, "const-long"); + + /** {@code r, literal: float :: r = literal;} */ + public static final Rop CONST_FLOAT = + new Rop(RegOps.CONST, Type.FLOAT, StdTypeList.EMPTY, "const-float"); + + /** {@code r, literal: double :: r = literal;} */ + public static final Rop CONST_DOUBLE = + new Rop(RegOps.CONST, Type.DOUBLE, StdTypeList.EMPTY, "const-double"); + + /** {@code r, literal: Object :: r = literal;} */ + public static final Rop CONST_OBJECT = + new Rop(RegOps.CONST, Type.OBJECT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "const-object"); + + /** {@code r, literal: Object :: r = literal;} */ + public static final Rop CONST_OBJECT_NOTHROW = + new Rop(RegOps.CONST, Type.OBJECT, StdTypeList.EMPTY, + "const-object-nothrow"); + + /** {@code goto label} */ + public static final Rop GOTO = + new Rop(RegOps.GOTO, Type.VOID, StdTypeList.EMPTY, Rop.BRANCH_GOTO, + "goto"); + + /** {@code x: int :: if (x == 0) goto label} */ + public static final Rop IF_EQZ_INT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-eqz-int"); + + /** {@code x: int :: if (x != 0) goto label} */ + public static final Rop IF_NEZ_INT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-nez-int"); + + /** {@code x: int :: if (x < 0) goto label} */ + public static final Rop IF_LTZ_INT = + new Rop(RegOps.IF_LT, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-ltz-int"); + + /** {@code x: int :: if (x >= 0) goto label} */ + public static final Rop IF_GEZ_INT = + new Rop(RegOps.IF_GE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-gez-int"); + + /** {@code x: int :: if (x <= 0) goto label} */ + public static final Rop IF_LEZ_INT = + new Rop(RegOps.IF_LE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-lez-int"); + + /** {@code x: int :: if (x > 0) goto label} */ + public static final Rop IF_GTZ_INT = + new Rop(RegOps.IF_GT, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-gtz-int"); + + /** {@code x: Object :: if (x == null) goto label} */ + public static final Rop IF_EQZ_OBJECT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.OBJECT, Rop.BRANCH_IF, + "if-eqz-object"); + + /** {@code x: Object :: if (x != null) goto label} */ + public static final Rop IF_NEZ_OBJECT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.OBJECT, Rop.BRANCH_IF, + "if-nez-object"); + + /** {@code x,y: int :: if (x == y) goto label} */ + public static final Rop IF_EQ_INT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-eq-int"); + + /** {@code x,y: int :: if (x != y) goto label} */ + public static final Rop IF_NE_INT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-ne-int"); + + /** {@code x,y: int :: if (x < y) goto label} */ + public static final Rop IF_LT_INT = + new Rop(RegOps.IF_LT, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-lt-int"); + + /** {@code x,y: int :: if (x >= y) goto label} */ + public static final Rop IF_GE_INT = + new Rop(RegOps.IF_GE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-ge-int"); + + /** {@code x,y: int :: if (x <= y) goto label} */ + public static final Rop IF_LE_INT = + new Rop(RegOps.IF_LE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-le-int"); + + /** {@code x,y: int :: if (x > y) goto label} */ + public static final Rop IF_GT_INT = + new Rop(RegOps.IF_GT, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-gt-int"); + + /** {@code x,y: Object :: if (x == y) goto label} */ + public static final Rop IF_EQ_OBJECT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.OBJECT_OBJECT, + Rop.BRANCH_IF, "if-eq-object"); + + /** {@code x,y: Object :: if (x != y) goto label} */ + public static final Rop IF_NE_OBJECT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.OBJECT_OBJECT, + Rop.BRANCH_IF, "if-ne-object"); + + /** {@code x: int :: goto switchtable[x]} */ + public static final Rop SWITCH = + new Rop(RegOps.SWITCH, Type.VOID, StdTypeList.INT, Rop.BRANCH_SWITCH, + "switch"); + + /** {@code r,x,y: int :: r = x + y;} */ + public static final Rop ADD_INT = + new Rop(RegOps.ADD, Type.INT, StdTypeList.INT_INT, "add-int"); + + /** {@code r,x,y: long :: r = x + y;} */ + public static final Rop ADD_LONG = + new Rop(RegOps.ADD, Type.LONG, StdTypeList.LONG_LONG, "add-long"); + + /** {@code r,x,y: float :: r = x + y;} */ + public static final Rop ADD_FLOAT = + new Rop(RegOps.ADD, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "add-float"); + + /** {@code r,x,y: double :: r = x + y;} */ + public static final Rop ADD_DOUBLE = + new Rop(RegOps.ADD, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + Rop.BRANCH_NONE, "add-double"); + + /** {@code r,x,y: int :: r = x - y;} */ + public static final Rop SUB_INT = + new Rop(RegOps.SUB, Type.INT, StdTypeList.INT_INT, "sub-int"); + + /** {@code r,x,y: long :: r = x - y;} */ + public static final Rop SUB_LONG = + new Rop(RegOps.SUB, Type.LONG, StdTypeList.LONG_LONG, "sub-long"); + + /** {@code r,x,y: float :: r = x - y;} */ + public static final Rop SUB_FLOAT = + new Rop(RegOps.SUB, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "sub-float"); + + /** {@code r,x,y: double :: r = x - y;} */ + public static final Rop SUB_DOUBLE = + new Rop(RegOps.SUB, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + Rop.BRANCH_NONE, "sub-double"); + + /** {@code r,x,y: int :: r = x * y;} */ + public static final Rop MUL_INT = + new Rop(RegOps.MUL, Type.INT, StdTypeList.INT_INT, "mul-int"); + + /** {@code r,x,y: long :: r = x * y;} */ + public static final Rop MUL_LONG = + new Rop(RegOps.MUL, Type.LONG, StdTypeList.LONG_LONG, "mul-long"); + + /** {@code r,x,y: float :: r = x * y;} */ + public static final Rop MUL_FLOAT = + new Rop(RegOps.MUL, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "mul-float"); + + /** {@code r,x,y: double :: r = x * y;} */ + public static final Rop MUL_DOUBLE = + new Rop(RegOps.MUL, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + Rop.BRANCH_NONE, "mul-double"); + + /** {@code r,x,y: int :: r = x / y;} */ + public static final Rop DIV_INT = + new Rop(RegOps.DIV, Type.INT, StdTypeList.INT_INT, + Exceptions.LIST_Error_ArithmeticException, "div-int"); + + /** {@code r,x,y: long :: r = x / y;} */ + public static final Rop DIV_LONG = + new Rop(RegOps.DIV, Type.LONG, StdTypeList.LONG_LONG, + Exceptions.LIST_Error_ArithmeticException, "div-long"); + + /** {@code r,x,y: float :: r = x / y;} */ + public static final Rop DIV_FLOAT = + new Rop(RegOps.DIV, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "div-float"); + + /** {@code r,x,y: double :: r = x / y;} */ + public static final Rop DIV_DOUBLE = + new Rop(RegOps.DIV, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + "div-double"); + + /** {@code r,x,y: int :: r = x % y;} */ + public static final Rop REM_INT = + new Rop(RegOps.REM, Type.INT, StdTypeList.INT_INT, + Exceptions.LIST_Error_ArithmeticException, "rem-int"); + + /** {@code r,x,y: long :: r = x % y;} */ + public static final Rop REM_LONG = + new Rop(RegOps.REM, Type.LONG, StdTypeList.LONG_LONG, + Exceptions.LIST_Error_ArithmeticException, "rem-long"); + + /** {@code r,x,y: float :: r = x % y;} */ + public static final Rop REM_FLOAT = + new Rop(RegOps.REM, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "rem-float"); + + /** {@code r,x,y: double :: r = x % y;} */ + public static final Rop REM_DOUBLE = + new Rop(RegOps.REM, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + "rem-double"); + + /** {@code r,x: int :: r = -x;} */ + public static final Rop NEG_INT = + new Rop(RegOps.NEG, Type.INT, StdTypeList.INT, "neg-int"); + + /** {@code r,x: long :: r = -x;} */ + public static final Rop NEG_LONG = + new Rop(RegOps.NEG, Type.LONG, StdTypeList.LONG, "neg-long"); + + /** {@code r,x: float :: r = -x;} */ + public static final Rop NEG_FLOAT = + new Rop(RegOps.NEG, Type.FLOAT, StdTypeList.FLOAT, "neg-float"); + + /** {@code r,x: double :: r = -x;} */ + public static final Rop NEG_DOUBLE = + new Rop(RegOps.NEG, Type.DOUBLE, StdTypeList.DOUBLE, "neg-double"); + + /** {@code r,x,y: int :: r = x & y;} */ + public static final Rop AND_INT = + new Rop(RegOps.AND, Type.INT, StdTypeList.INT_INT, "and-int"); + + /** {@code r,x,y: long :: r = x & y;} */ + public static final Rop AND_LONG = + new Rop(RegOps.AND, Type.LONG, StdTypeList.LONG_LONG, "and-long"); + + /** {@code r,x,y: int :: r = x | y;} */ + public static final Rop OR_INT = + new Rop(RegOps.OR, Type.INT, StdTypeList.INT_INT, "or-int"); + + /** {@code r,x,y: long :: r = x | y;} */ + public static final Rop OR_LONG = + new Rop(RegOps.OR, Type.LONG, StdTypeList.LONG_LONG, "or-long"); + + /** {@code r,x,y: int :: r = x ^ y;} */ + public static final Rop XOR_INT = + new Rop(RegOps.XOR, Type.INT, StdTypeList.INT_INT, "xor-int"); + + /** {@code r,x,y: long :: r = x ^ y;} */ + public static final Rop XOR_LONG = + new Rop(RegOps.XOR, Type.LONG, StdTypeList.LONG_LONG, "xor-long"); + + /** {@code r,x,y: int :: r = x << y;} */ + public static final Rop SHL_INT = + new Rop(RegOps.SHL, Type.INT, StdTypeList.INT_INT, "shl-int"); + + /** {@code r,x: long; y: int :: r = x << y;} */ + public static final Rop SHL_LONG = + new Rop(RegOps.SHL, Type.LONG, StdTypeList.LONG_INT, "shl-long"); + + /** {@code r,x,y: int :: r = x >> y;} */ + public static final Rop SHR_INT = + new Rop(RegOps.SHR, Type.INT, StdTypeList.INT_INT, "shr-int"); + + /** {@code r,x: long; y: int :: r = x >> y;} */ + public static final Rop SHR_LONG = + new Rop(RegOps.SHR, Type.LONG, StdTypeList.LONG_INT, "shr-long"); + + /** {@code r,x,y: int :: r = x >>> y;} */ + public static final Rop USHR_INT = + new Rop(RegOps.USHR, Type.INT, StdTypeList.INT_INT, "ushr-int"); + + /** {@code r,x: long; y: int :: r = x >>> y;} */ + public static final Rop USHR_LONG = + new Rop(RegOps.USHR, Type.LONG, StdTypeList.LONG_INT, "ushr-long"); + + /** {@code r,x: int :: r = ~x;} */ + public static final Rop NOT_INT = + new Rop(RegOps.NOT, Type.INT, StdTypeList.INT, "not-int"); + + /** {@code r,x: long :: r = ~x;} */ + public static final Rop NOT_LONG = + new Rop(RegOps.NOT, Type.LONG, StdTypeList.LONG, "not-long"); + + /** {@code r,x,c: int :: r = x + c;} */ + public static final Rop ADD_CONST_INT = + new Rop(RegOps.ADD, Type.INT, StdTypeList.INT, "add-const-int"); + + /** {@code r,x,c: long :: r = x + c;} */ + public static final Rop ADD_CONST_LONG = + new Rop(RegOps.ADD, Type.LONG, StdTypeList.LONG, "add-const-long"); + + /** {@code r,x,c: float :: r = x + c;} */ + public static final Rop ADD_CONST_FLOAT = + new Rop(RegOps.ADD, Type.FLOAT, StdTypeList.FLOAT, "add-const-float"); + + /** {@code r,x,c: double :: r = x + c;} */ + public static final Rop ADD_CONST_DOUBLE = + new Rop(RegOps.ADD, Type.DOUBLE, StdTypeList.DOUBLE, + "add-const-double"); + + /** {@code r,x,c: int :: r = x - c;} */ + public static final Rop SUB_CONST_INT = + new Rop(RegOps.SUB, Type.INT, StdTypeList.INT, "sub-const-int"); + + /** {@code r,x,c: long :: r = x - c;} */ + public static final Rop SUB_CONST_LONG = + new Rop(RegOps.SUB, Type.LONG, StdTypeList.LONG, "sub-const-long"); + + /** {@code r,x,c: float :: r = x - c;} */ + public static final Rop SUB_CONST_FLOAT = + new Rop(RegOps.SUB, Type.FLOAT, StdTypeList.FLOAT, "sub-const-float"); + + /** {@code r,x,c: double :: r = x - c;} */ + public static final Rop SUB_CONST_DOUBLE = + new Rop(RegOps.SUB, Type.DOUBLE, StdTypeList.DOUBLE, + "sub-const-double"); + + /** {@code r,x,c: int :: r = x * c;} */ + public static final Rop MUL_CONST_INT = + new Rop(RegOps.MUL, Type.INT, StdTypeList.INT, "mul-const-int"); + + /** {@code r,x,c: long :: r = x * c;} */ + public static final Rop MUL_CONST_LONG = + new Rop(RegOps.MUL, Type.LONG, StdTypeList.LONG, "mul-const-long"); + + /** {@code r,x,c: float :: r = x * c;} */ + public static final Rop MUL_CONST_FLOAT = + new Rop(RegOps.MUL, Type.FLOAT, StdTypeList.FLOAT, "mul-const-float"); + + /** {@code r,x,c: double :: r = x * c;} */ + public static final Rop MUL_CONST_DOUBLE = + new Rop(RegOps.MUL, Type.DOUBLE, StdTypeList.DOUBLE, + "mul-const-double"); + + /** {@code r,x,c: int :: r = x / c;} */ + public static final Rop DIV_CONST_INT = + new Rop(RegOps.DIV, Type.INT, StdTypeList.INT, + Exceptions.LIST_Error_ArithmeticException, "div-const-int"); + + /** {@code r,x,c: long :: r = x / c;} */ + public static final Rop DIV_CONST_LONG = + new Rop(RegOps.DIV, Type.LONG, StdTypeList.LONG, + Exceptions.LIST_Error_ArithmeticException, "div-const-long"); + + /** {@code r,x,c: float :: r = x / c;} */ + public static final Rop DIV_CONST_FLOAT = + new Rop(RegOps.DIV, Type.FLOAT, StdTypeList.FLOAT, "div-const-float"); + + /** {@code r,x,c: double :: r = x / c;} */ + public static final Rop DIV_CONST_DOUBLE = + new Rop(RegOps.DIV, Type.DOUBLE, StdTypeList.DOUBLE, + "div-const-double"); + + /** {@code r,x,c: int :: r = x % c;} */ + public static final Rop REM_CONST_INT = + new Rop(RegOps.REM, Type.INT, StdTypeList.INT, + Exceptions.LIST_Error_ArithmeticException, "rem-const-int"); + + /** {@code r,x,c: long :: r = x % c;} */ + public static final Rop REM_CONST_LONG = + new Rop(RegOps.REM, Type.LONG, StdTypeList.LONG, + Exceptions.LIST_Error_ArithmeticException, "rem-const-long"); + + /** {@code r,x,c: float :: r = x % c;} */ + public static final Rop REM_CONST_FLOAT = + new Rop(RegOps.REM, Type.FLOAT, StdTypeList.FLOAT, "rem-const-float"); + + /** {@code r,x,c: double :: r = x % c;} */ + public static final Rop REM_CONST_DOUBLE = + new Rop(RegOps.REM, Type.DOUBLE, StdTypeList.DOUBLE, + "rem-const-double"); + + /** {@code r,x,c: int :: r = x & c;} */ + public static final Rop AND_CONST_INT = + new Rop(RegOps.AND, Type.INT, StdTypeList.INT, "and-const-int"); + + /** {@code r,x,c: long :: r = x & c;} */ + public static final Rop AND_CONST_LONG = + new Rop(RegOps.AND, Type.LONG, StdTypeList.LONG, "and-const-long"); + + /** {@code r,x,c: int :: r = x | c;} */ + public static final Rop OR_CONST_INT = + new Rop(RegOps.OR, Type.INT, StdTypeList.INT, "or-const-int"); + + /** {@code r,x,c: long :: r = x | c;} */ + public static final Rop OR_CONST_LONG = + new Rop(RegOps.OR, Type.LONG, StdTypeList.LONG, "or-const-long"); + + /** {@code r,x,c: int :: r = x ^ c;} */ + public static final Rop XOR_CONST_INT = + new Rop(RegOps.XOR, Type.INT, StdTypeList.INT, "xor-const-int"); + + /** {@code r,x,c: long :: r = x ^ c;} */ + public static final Rop XOR_CONST_LONG = + new Rop(RegOps.XOR, Type.LONG, StdTypeList.LONG, "xor-const-long"); + + /** {@code r,x,c: int :: r = x << c;} */ + public static final Rop SHL_CONST_INT = + new Rop(RegOps.SHL, Type.INT, StdTypeList.INT, "shl-const-int"); + + /** {@code r,x: long; c: int :: r = x << c;} */ + public static final Rop SHL_CONST_LONG = + new Rop(RegOps.SHL, Type.LONG, StdTypeList.INT, "shl-const-long"); + + /** {@code r,x,c: int :: r = x >> c;} */ + public static final Rop SHR_CONST_INT = + new Rop(RegOps.SHR, Type.INT, StdTypeList.INT, "shr-const-int"); + + /** {@code r,x: long; c: int :: r = x >> c;} */ + public static final Rop SHR_CONST_LONG = + new Rop(RegOps.SHR, Type.LONG, StdTypeList.INT, "shr-const-long"); + + /** {@code r,x,c: int :: r = x >>> c;} */ + public static final Rop USHR_CONST_INT = + new Rop(RegOps.USHR, Type.INT, StdTypeList.INT, "ushr-const-int"); + + /** {@code r,x: long; c: int :: r = x >>> c;} */ + public static final Rop USHR_CONST_LONG = + new Rop(RegOps.USHR, Type.LONG, StdTypeList.INT, "ushr-const-long"); + + /** {@code r: int; x,y: long :: r = cmp(x, y);} */ + public static final Rop CMPL_LONG = + new Rop(RegOps.CMPL, Type.INT, StdTypeList.LONG_LONG, "cmpl-long"); + + /** {@code r: int; x,y: float :: r = cmpl(x, y);} */ + public static final Rop CMPL_FLOAT = + new Rop(RegOps.CMPL, Type.INT, StdTypeList.FLOAT_FLOAT, "cmpl-float"); + + /** {@code r: int; x,y: double :: r = cmpl(x, y);} */ + public static final Rop CMPL_DOUBLE = + new Rop(RegOps.CMPL, Type.INT, StdTypeList.DOUBLE_DOUBLE, + "cmpl-double"); + + /** {@code r: int; x,y: float :: r = cmpg(x, y);} */ + public static final Rop CMPG_FLOAT = + new Rop(RegOps.CMPG, Type.INT, StdTypeList.FLOAT_FLOAT, "cmpg-float"); + + /** {@code r: int; x,y: double :: r = cmpg(x, y);} */ + public static final Rop CMPG_DOUBLE = + new Rop(RegOps.CMPG, Type.INT, StdTypeList.DOUBLE_DOUBLE, + "cmpg-double"); + + /** {@code r: int; x: long :: r = (int) x} */ + public static final Rop CONV_L2I = + new Rop(RegOps.CONV, Type.INT, StdTypeList.LONG, "conv-l2i"); + + /** {@code r: int; x: float :: r = (int) x} */ + public static final Rop CONV_F2I = + new Rop(RegOps.CONV, Type.INT, StdTypeList.FLOAT, "conv-f2i"); + + /** {@code r: int; x: double :: r = (int) x} */ + public static final Rop CONV_D2I = + new Rop(RegOps.CONV, Type.INT, StdTypeList.DOUBLE, "conv-d2i"); + + /** {@code r: long; x: int :: r = (long) x} */ + public static final Rop CONV_I2L = + new Rop(RegOps.CONV, Type.LONG, StdTypeList.INT, "conv-i2l"); + + /** {@code r: long; x: float :: r = (long) x} */ + public static final Rop CONV_F2L = + new Rop(RegOps.CONV, Type.LONG, StdTypeList.FLOAT, "conv-f2l"); + + /** {@code r: long; x: double :: r = (long) x} */ + public static final Rop CONV_D2L = + new Rop(RegOps.CONV, Type.LONG, StdTypeList.DOUBLE, "conv-d2l"); + + /** {@code r: float; x: int :: r = (float) x} */ + public static final Rop CONV_I2F = + new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.INT, "conv-i2f"); + + /** {@code r: float; x: long :: r = (float) x} */ + public static final Rop CONV_L2F = + new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.LONG, "conv-l2f"); + + /** {@code r: float; x: double :: r = (float) x} */ + public static final Rop CONV_D2F = + new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.DOUBLE, "conv-d2f"); + + /** {@code r: double; x: int :: r = (double) x} */ + public static final Rop CONV_I2D = + new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.INT, "conv-i2d"); + + /** {@code r: double; x: long :: r = (double) x} */ + public static final Rop CONV_L2D = + new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.LONG, "conv-l2d"); + + /** {@code r: double; x: float :: r = (double) x} */ + public static final Rop CONV_F2D = + new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.FLOAT, "conv-f2d"); + + /** + * {@code r,x: int :: r = (x << 24) >> 24} (Java-style + * convert int to byte) + */ + public static final Rop TO_BYTE = + new Rop(RegOps.TO_BYTE, Type.INT, StdTypeList.INT, "to-byte"); + + /** + * {@code r,x: int :: r = x & 0xffff} (Java-style + * convert int to char) + */ + public static final Rop TO_CHAR = + new Rop(RegOps.TO_CHAR, Type.INT, StdTypeList.INT, "to-char"); + + /** + * {@code r,x: int :: r = (x << 16) >> 16} (Java-style + * convert int to short) + */ + public static final Rop TO_SHORT = + new Rop(RegOps.TO_SHORT, Type.INT, StdTypeList.INT, "to-short"); + + /** {@code return void} */ + public static final Rop RETURN_VOID = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.EMPTY, Rop.BRANCH_RETURN, + "return-void"); + + /** {@code x: int; return x} */ + public static final Rop RETURN_INT = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.INT, Rop.BRANCH_RETURN, + "return-int"); + + /** {@code x: long; return x} */ + public static final Rop RETURN_LONG = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.LONG, Rop.BRANCH_RETURN, + "return-long"); + + /** {@code x: float; return x} */ + public static final Rop RETURN_FLOAT = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.FLOAT, Rop.BRANCH_RETURN, + "return-float"); + + /** {@code x: double; return x} */ + public static final Rop RETURN_DOUBLE = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.DOUBLE, + Rop.BRANCH_RETURN, "return-double"); + + /** {@code x: Object; return x} */ + public static final Rop RETURN_OBJECT = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.OBJECT, + Rop.BRANCH_RETURN, "return-object"); + + /** {@code T: any type; r: int; x: T[]; :: r = x.length} */ + public static final Rop ARRAY_LENGTH = + new Rop(RegOps.ARRAY_LENGTH, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "array-length"); + + /** {@code x: Throwable :: throw(x)} */ + public static final Rop THROW = + new Rop(RegOps.THROW, Type.VOID, StdTypeList.THROWABLE, + StdTypeList.THROWABLE, "throw"); + + /** {@code x: Object :: monitorenter(x)} */ + public static final Rop MONITOR_ENTER = + new Rop(RegOps.MONITOR_ENTER, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "monitor-enter"); + + /** {@code x: Object :: monitorexit(x)} */ + public static final Rop MONITOR_EXIT = + new Rop(RegOps.MONITOR_EXIT, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error_Null_IllegalMonitorStateException, + "monitor-exit"); + + /** {@code r,y: int; x: int[] :: r = x[y]} */ + public static final Rop AGET_INT = + new Rop(RegOps.AGET, Type.INT, StdTypeList.INTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-int"); + + /** {@code r: long; x: long[]; y: int :: r = x[y]} */ + public static final Rop AGET_LONG = + new Rop(RegOps.AGET, Type.LONG, StdTypeList.LONGARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-long"); + + /** {@code r: float; x: float[]; y: int :: r = x[y]} */ + public static final Rop AGET_FLOAT = + new Rop(RegOps.AGET, Type.FLOAT, StdTypeList.FLOATARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-float"); + + /** {@code r: double; x: double[]; y: int :: r = x[y]} */ + public static final Rop AGET_DOUBLE = + new Rop(RegOps.AGET, Type.DOUBLE, StdTypeList.DOUBLEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-double"); + + /** {@code r: Object; x: Object[]; y: int :: r = x[y]} */ + public static final Rop AGET_OBJECT = + new Rop(RegOps.AGET, Type.OBJECT, StdTypeList.OBJECTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-object"); + + /** {@code r: boolean; x: boolean[]; y: int :: r = x[y]} */ + public static final Rop AGET_BOOLEAN = + new Rop(RegOps.AGET, Type.INT, StdTypeList.BOOLEANARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-boolean"); + + /** {@code r: byte; x: byte[]; y: int :: r = x[y]} */ + public static final Rop AGET_BYTE = + new Rop(RegOps.AGET, Type.INT, StdTypeList.BYTEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aget-byte"); + + /** {@code r: char; x: char[]; y: int :: r = x[y]} */ + public static final Rop AGET_CHAR = + new Rop(RegOps.AGET, Type.INT, StdTypeList.CHARARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aget-char"); + + /** {@code r: short; x: short[]; y: int :: r = x[y]} */ + public static final Rop AGET_SHORT = + new Rop(RegOps.AGET, Type.INT, StdTypeList.SHORTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-short"); + + /** {@code x,z: int; y: int[] :: y[z] = x} */ + public static final Rop APUT_INT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_INTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aput-int"); + + /** {@code x: long; y: long[]; z: int :: y[z] = x} */ + public static final Rop APUT_LONG = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.LONG_LONGARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aput-long"); + + /** {@code x: float; y: float[]; z: int :: y[z] = x} */ + public static final Rop APUT_FLOAT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.FLOAT_FLOATARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aput-float"); + + /** {@code x: double; y: double[]; z: int :: y[z] = x} */ + public static final Rop APUT_DOUBLE = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.DOUBLE_DOUBLEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aput-double"); + + /** {@code x: Object; y: Object[]; z: int :: y[z] = x} */ + public static final Rop APUT_OBJECT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.OBJECT_OBJECTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, + "aput-object"); + + /** {@code x: boolean; y: boolean[]; z: int :: y[z] = x} */ + public static final Rop APUT_BOOLEAN = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_BOOLEANARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, + "aput-boolean"); + + /** {@code x: byte; y: byte[]; z: int :: y[z] = x} */ + public static final Rop APUT_BYTE = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_BYTEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, "aput-byte"); + + /** {@code x: char; y: char[]; z: int :: y[z] = x} */ + public static final Rop APUT_CHAR = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_CHARARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, "aput-char"); + + /** {@code x: short; y: short[]; z: int :: y[z] = x} */ + public static final Rop APUT_SHORT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_SHORTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, + "aput-short"); + + /** + * {@code T: any non-array object type :: r = + * alloc(T)} (allocate heap space for an object) + */ + public static final Rop NEW_INSTANCE = + new Rop(RegOps.NEW_INSTANCE, Type.OBJECT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "new-instance"); + + /** {@code r: int[]; x: int :: r = new int[x]} */ + public static final Rop NEW_ARRAY_INT = + new Rop(RegOps.NEW_ARRAY, Type.INT_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-int"); + + /** {@code r: long[]; x: int :: r = new long[x]} */ + public static final Rop NEW_ARRAY_LONG = + new Rop(RegOps.NEW_ARRAY, Type.LONG_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-long"); + + /** {@code r: float[]; x: int :: r = new float[x]} */ + public static final Rop NEW_ARRAY_FLOAT = + new Rop(RegOps.NEW_ARRAY, Type.FLOAT_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-float"); + + /** {@code r: double[]; x: int :: r = new double[x]} */ + public static final Rop NEW_ARRAY_DOUBLE = + new Rop(RegOps.NEW_ARRAY, Type.DOUBLE_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-double"); + + /** {@code r: boolean[]; x: int :: r = new boolean[x]} */ + public static final Rop NEW_ARRAY_BOOLEAN = + new Rop(RegOps.NEW_ARRAY, Type.BOOLEAN_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-boolean"); + + /** {@code r: byte[]; x: int :: r = new byte[x]} */ + public static final Rop NEW_ARRAY_BYTE = + new Rop(RegOps.NEW_ARRAY, Type.BYTE_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-byte"); + + /** {@code r: char[]; x: int :: r = new char[x]} */ + public static final Rop NEW_ARRAY_CHAR = + new Rop(RegOps.NEW_ARRAY, Type.CHAR_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-char"); + + /** {@code r: short[]; x: int :: r = new short[x]} */ + public static final Rop NEW_ARRAY_SHORT = + new Rop(RegOps.NEW_ARRAY, Type.SHORT_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-short"); + + /** + * {@code T: any non-array object type; x: Object :: (T) x} (can + * throw {@code ClassCastException}) + */ + public static final Rop CHECK_CAST = + new Rop(RegOps.CHECK_CAST, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error_ClassCastException, "check-cast"); + + /** + * {@code T: any non-array object type; x: Object :: x instanceof + * T}. Note: This is listed as throwing {@code Error} + * explicitly because the op can throw, but there are no + * other predefined exceptions for it. + */ + public static final Rop INSTANCE_OF = + new Rop(RegOps.INSTANCE_OF, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error, "instance-of"); + + /** + * {@code r: int; x: Object; f: instance field spec of + * type int :: r = x.f} + */ + public static final Rop GET_FIELD_INT = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "get-field-int"); + + /** + * {@code r: long; x: Object; f: instance field spec of + * type long :: r = x.f} + */ + public static final Rop GET_FIELD_LONG = + new Rop(RegOps.GET_FIELD, Type.LONG, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "get-field-long"); + + /** + * {@code r: float; x: Object; f: instance field spec of + * type float :: r = x.f} + */ + public static final Rop GET_FIELD_FLOAT = + new Rop(RegOps.GET_FIELD, Type.FLOAT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-float"); + + /** + * {@code r: double; x: Object; f: instance field spec of + * type double :: r = x.f} + */ + public static final Rop GET_FIELD_DOUBLE = + new Rop(RegOps.GET_FIELD, Type.DOUBLE, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-double"); + + /** + * {@code r: Object; x: Object; f: instance field spec of + * type Object :: r = x.f} + */ + public static final Rop GET_FIELD_OBJECT = + new Rop(RegOps.GET_FIELD, Type.OBJECT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-object"); + + /** + * {@code r: boolean; x: Object; f: instance field spec of + * type boolean :: r = x.f} + */ + public static final Rop GET_FIELD_BOOLEAN = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-boolean"); + + /** + * {@code r: byte; x: Object; f: instance field spec of + * type byte :: r = x.f} + */ + public static final Rop GET_FIELD_BYTE = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-byte"); + + /** + * {@code r: char; x: Object; f: instance field spec of + * type char :: r = x.f} + */ + public static final Rop GET_FIELD_CHAR = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-char"); + + /** + * {@code r: short; x: Object; f: instance field spec of + * type short :: r = x.f} + */ + public static final Rop GET_FIELD_SHORT = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-short"); + + /** {@code r: int; f: static field spec of type int :: r = f} */ + public static final Rop GET_STATIC_INT = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-int"); + + /** {@code r: long; f: static field spec of type long :: r = f} */ + public static final Rop GET_STATIC_LONG = + new Rop(RegOps.GET_STATIC, Type.LONG, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-long"); + + /** {@code r: float; f: static field spec of type float :: r = f} */ + public static final Rop GET_STATIC_FLOAT = + new Rop(RegOps.GET_STATIC, Type.FLOAT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-float"); + + /** {@code r: double; f: static field spec of type double :: r = f} */ + public static final Rop GET_STATIC_DOUBLE = + new Rop(RegOps.GET_STATIC, Type.DOUBLE, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-double"); + + /** {@code r: Object; f: static field spec of type Object :: r = f} */ + public static final Rop GET_STATIC_OBJECT = + new Rop(RegOps.GET_STATIC, Type.OBJECT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-object"); + + /** {@code r: boolean; f: static field spec of type boolean :: r = f} */ + public static final Rop GET_STATIC_BOOLEAN = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-boolean"); + + /** {@code r: byte; f: static field spec of type byte :: r = f} */ + public static final Rop GET_STATIC_BYTE = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-byte"); + + /** {@code r: char; f: static field spec of type char :: r = f} */ + public static final Rop GET_STATIC_CHAR = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-char"); + + /** {@code r: short; f: static field spec of type short :: r = f} */ + public static final Rop GET_STATIC_SHORT = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-short"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * int :: y.f = x} + */ + public static final Rop PUT_FIELD_INT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, "put-field-int"); + + /** + * {@code x: long; y: Object; f: instance field spec of type + * long :: y.f = x} + */ + public static final Rop PUT_FIELD_LONG = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.LONG_OBJECT, + Exceptions.LIST_Error_NullPointerException, "put-field-long"); + + /** + * {@code x: float; y: Object; f: instance field spec of type + * float :: y.f = x} + */ + public static final Rop PUT_FIELD_FLOAT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.FLOAT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-float"); + + /** + * {@code x: double; y: Object; f: instance field spec of type + * double :: y.f = x} + */ + public static final Rop PUT_FIELD_DOUBLE = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.DOUBLE_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-double"); + + /** + * {@code x: Object; y: Object; f: instance field spec of type + * Object :: y.f = x} + */ + public static final Rop PUT_FIELD_OBJECT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.OBJECT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-object"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * boolean :: y.f = x} + */ + public static final Rop PUT_FIELD_BOOLEAN = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-boolean"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * byte :: y.f = x} + */ + public static final Rop PUT_FIELD_BYTE = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-byte"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * char :: y.f = x} + */ + public static final Rop PUT_FIELD_CHAR = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-char"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * short :: y.f = x} + */ + public static final Rop PUT_FIELD_SHORT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-short"); + + /** {@code f: static field spec of type int; x: int :: f = x} */ + public static final Rop PUT_STATIC_INT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-int"); + + /** {@code f: static field spec of type long; x: long :: f = x} */ + public static final Rop PUT_STATIC_LONG = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.LONG, + Exceptions.LIST_Error, "put-static-long"); + + /** {@code f: static field spec of type float; x: float :: f = x} */ + public static final Rop PUT_STATIC_FLOAT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.FLOAT, + Exceptions.LIST_Error, "put-static-float"); + + /** {@code f: static field spec of type double; x: double :: f = x} */ + public static final Rop PUT_STATIC_DOUBLE = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.DOUBLE, + Exceptions.LIST_Error, "put-static-double"); + + /** {@code f: static field spec of type Object; x: Object :: f = x} */ + public static final Rop PUT_STATIC_OBJECT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error, "put-static-object"); + + /** + * {@code f: static field spec of type boolean; x: boolean :: f = + * x} + */ + public static final Rop PUT_STATIC_BOOLEAN = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-boolean"); + + /** {@code f: static field spec of type byte; x: byte :: f = x} */ + public static final Rop PUT_STATIC_BYTE = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-byte"); + + /** {@code f: static field spec of type char; x: char :: f = x} */ + public static final Rop PUT_STATIC_CHAR = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-char"); + + /** {@code f: static field spec of type short; x: short :: f = x} */ + public static final Rop PUT_STATIC_SHORT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-short"); + + /** {@code x: Int :: local variable begins in x} */ + public static final Rop MARK_LOCAL_INT = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.INT, "mark-local-int"); + + /** {@code x: Long :: local variable begins in x} */ + public static final Rop MARK_LOCAL_LONG = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.LONG, "mark-local-long"); + + /** {@code x: Float :: local variable begins in x} */ + public static final Rop MARK_LOCAL_FLOAT = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.FLOAT, "mark-local-float"); + + /** {@code x: Double :: local variable begins in x} */ + public static final Rop MARK_LOCAL_DOUBLE = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.DOUBLE, "mark-local-double"); + + /** {@code x: Object :: local variable begins in x} */ + public static final Rop MARK_LOCAL_OBJECT = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.OBJECT, "mark-local-object"); + + /** {@code T: Any primitive type; v0..vx: T :: {v0, ..., vx}} */ + public static final Rop FILL_ARRAY_DATA = + new Rop(RegOps.FILL_ARRAY_DATA, Type.VOID, StdTypeList.EMPTY, + "fill-array-data"); + + /** + * Returns the appropriate rop for the given opcode, destination, + * and sources. The result is typically, but not necessarily, a + * shared instance. + * + *

Note: This method does not do complete error checking on + * its arguments, and so it may return an instance which seemed "right + * enough" even though in actuality the passed arguments don't quite + * match what is returned. TODO: Revisit this issue.

+ * + * @param opcode the opcode + * @param dest {@code non-null;} destination (result) type, or + * {@link Type#VOID} if none + * @param sources {@code non-null;} list of source types + * @param cst {@code null-ok;} associated constant, if any + * @return {@code non-null;} an appropriate instance + */ + public static Rop ropFor(int opcode, TypeBearer dest, TypeList sources, + Constant cst) { + switch (opcode) { + case RegOps.NOP: return NOP; + case RegOps.MOVE: return opMove(dest); + case RegOps.MOVE_PARAM: return opMoveParam(dest); + case RegOps.MOVE_EXCEPTION: return opMoveException(dest); + case RegOps.CONST: return opConst(dest); + case RegOps.GOTO: return GOTO; + case RegOps.IF_EQ: return opIfEq(sources); + case RegOps.IF_NE: return opIfNe(sources); + case RegOps.IF_LT: return opIfLt(sources); + case RegOps.IF_GE: return opIfGe(sources); + case RegOps.IF_LE: return opIfLe(sources); + case RegOps.IF_GT: return opIfGt(sources); + case RegOps.SWITCH: return SWITCH; + case RegOps.ADD: return opAdd(sources); + case RegOps.SUB: return opSub(sources); + case RegOps.MUL: return opMul(sources); + case RegOps.DIV: return opDiv(sources); + case RegOps.REM: return opRem(sources); + case RegOps.NEG: return opNeg(dest); + case RegOps.AND: return opAnd(sources); + case RegOps.OR: return opOr(sources); + case RegOps.XOR: return opXor(sources); + case RegOps.SHL: return opShl(sources); + case RegOps.SHR: return opShr(sources); + case RegOps.USHR: return opUshr(sources); + case RegOps.NOT: return opNot(dest); + case RegOps.CMPL: return opCmpl(sources.getType(0)); + case RegOps.CMPG: return opCmpg(sources.getType(0)); + case RegOps.CONV: return opConv(dest, sources.getType(0)); + case RegOps.TO_BYTE: return TO_BYTE; + case RegOps.TO_CHAR: return TO_CHAR; + case RegOps.TO_SHORT: return TO_SHORT; + case RegOps.RETURN: { + if (sources.size() == 0) { + return RETURN_VOID; + } + return opReturn(sources.getType(0)); + } + case RegOps.ARRAY_LENGTH: return ARRAY_LENGTH; + case RegOps.THROW: return THROW; + case RegOps.MONITOR_ENTER: return MONITOR_ENTER; + case RegOps.MONITOR_EXIT: return MONITOR_EXIT; + case RegOps.AGET: { + Type source = sources.getType(0); + Type componentType; + if (source == Type.KNOWN_NULL) { + /* + * Treat a known-null as an array of the expected + * result type. + */ + componentType = dest.getType(); + } else { + componentType = source.getComponentType(); + } + return opAget(componentType); + } + case RegOps.APUT: { + Type source = sources.getType(1); + Type componentType; + if (source == Type.KNOWN_NULL) { + /* + * Treat a known-null as an array of the type being + * stored. + */ + componentType = sources.getType(0); + } else { + componentType = source.getComponentType(); + } + return opAput(componentType); + } + case RegOps.NEW_INSTANCE: return NEW_INSTANCE; + case RegOps.NEW_ARRAY: return opNewArray(dest.getType()); + case RegOps.CHECK_CAST: return CHECK_CAST; + case RegOps.INSTANCE_OF: return INSTANCE_OF; + case RegOps.GET_FIELD: return opGetField(dest); + case RegOps.GET_STATIC: return opGetStatic(dest); + case RegOps.PUT_FIELD: return opPutField(sources.getType(0)); + case RegOps.PUT_STATIC: return opPutStatic(sources.getType(0)); + case RegOps.INVOKE_STATIC: { + return opInvokeStatic(((CstMethodRef) cst).getPrototype()); + } + case RegOps.INVOKE_VIRTUAL: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeVirtual(meth); + } + case RegOps.INVOKE_SUPER: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeSuper(meth); + } + case RegOps.INVOKE_DIRECT: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeDirect(meth); + } + case RegOps.INVOKE_INTERFACE: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeInterface(meth); + } + case RegOps.INVOKE_POLYMORPHIC: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype proto = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + Prototype meth = proto.withFirstParameter(definer.getClassType()); + return opInvokePolymorphic(meth); + } + } + + throw new RuntimeException("unknown opcode " + RegOps.opName(opcode)); + } + + /** + * Returns the appropriate {@code move} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being moved + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMove(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return MOVE_INT; + case Type.BT_LONG: return MOVE_LONG; + case Type.BT_FLOAT: return MOVE_FLOAT; + case Type.BT_DOUBLE: return MOVE_DOUBLE; + case Type.BT_OBJECT: return MOVE_OBJECT; + case Type.BT_ADDR: return MOVE_RETURN_ADDRESS; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code move-param} rop for the + * given type. The result is a shared instance. + * + * @param type {@code non-null;} type of value being moved + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveParam(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return MOVE_PARAM_INT; + case Type.BT_LONG: return MOVE_PARAM_LONG; + case Type.BT_FLOAT: return MOVE_PARAM_FLOAT; + case Type.BT_DOUBLE: return MOVE_PARAM_DOUBLE; + case Type.BT_OBJECT: return MOVE_PARAM_OBJECT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code move-exception} rop for the + * given type. The result may be a shared instance. + * + * @param type {@code non-null;} type of the exception + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveException(TypeBearer type) { + return new Rop(RegOps.MOVE_EXCEPTION, type.getType(), + StdTypeList.EMPTY, (String) null); + } + + /** + * Returns the appropriate {@code move-result} rop for the + * given type. The result may be a shared instance. + * + * @param type {@code non-null;} type of the parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveResult(TypeBearer type) { + return new Rop(RegOps.MOVE_RESULT, type.getType(), + StdTypeList.EMPTY, (String) null); + } + + /** + * Returns the appropriate {@code move-result-pseudo} rop for the + * given type. The result may be a shared instance. + * + * @param type {@code non-null;} type of the parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveResultPseudo(TypeBearer type) { + return new Rop(RegOps.MOVE_RESULT_PSEUDO, type.getType(), + StdTypeList.EMPTY, (String) null); + } + + /** + * Returns the appropriate {@code const} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the constant + * @return {@code non-null;} an appropriate instance + */ + public static Rop opConst(TypeBearer type) { + if (type.getType() == Type.KNOWN_NULL) { + return CONST_OBJECT_NOTHROW; + } + + switch (type.getBasicFrameType()) { + case Type.BT_INT: return CONST_INT; + case Type.BT_LONG: return CONST_LONG; + case Type.BT_FLOAT: return CONST_FLOAT; + case Type.BT_DOUBLE: return CONST_DOUBLE; + case Type.BT_OBJECT: return CONST_OBJECT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code if-eq} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfEq(TypeList types) { + return pickIf(types, IF_EQZ_INT, IF_EQZ_OBJECT, + IF_EQ_INT, IF_EQ_OBJECT); + } + + /** + * Returns the appropriate {@code if-ne} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfNe(TypeList types) { + return pickIf(types, IF_NEZ_INT, IF_NEZ_OBJECT, + IF_NE_INT, IF_NE_OBJECT); + } + + /** + * Returns the appropriate {@code if-lt} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfLt(TypeList types) { + return pickIf(types, IF_LTZ_INT, null, IF_LT_INT, null); + } + + /** + * Returns the appropriate {@code if-ge} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfGe(TypeList types) { + return pickIf(types, IF_GEZ_INT, null, IF_GE_INT, null); + } + + /** + * Returns the appropriate {@code if-gt} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfGt(TypeList types) { + return pickIf(types, IF_GTZ_INT, null, IF_GT_INT, null); + } + + /** + * Returns the appropriate {@code if-le} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfLe(TypeList types) { + return pickIf(types, IF_LEZ_INT, null, IF_LE_INT, null); + } + + /** + * Helper for all the {@code if*}-related methods, which + * checks types and picks one of the four variants, throwing if + * there's a problem. + * + * @param types {@code non-null;} the types + * @param intZ {@code non-null;} the int-to-0 comparison + * @param objZ {@code null-ok;} the object-to-null comparison + * @param intInt {@code non-null;} the int-to-int comparison + * @param objObj {@code non-null;} the object-to-object comparison + * @return {@code non-null;} the appropriate instance + */ + private static Rop pickIf(TypeList types, Rop intZ, Rop objZ, Rop intInt, + Rop objObj) { + switch(types.size()) { + case 1: { + switch (types.getType(0).getBasicFrameType()) { + case Type.BT_INT: { + return intZ; + } + case Type.BT_OBJECT: { + if (objZ != null) { + return objZ; + } + } + } + break; + } + case 2: { + int bt = types.getType(0).getBasicFrameType(); + if (bt == types.getType(1).getBasicFrameType()) { + switch (bt) { + case Type.BT_INT: { + return intInt; + } + case Type.BT_OBJECT: { + if (objObj != null) { + return objObj; + } + } + } + } + break; + } + } + + return throwBadTypes(types); + } + + /** + * Returns the appropriate {@code add} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAdd(TypeList types) { + return pickBinaryOp(types, ADD_CONST_INT, ADD_CONST_LONG, + ADD_CONST_FLOAT, ADD_CONST_DOUBLE, ADD_INT, + ADD_LONG, ADD_FLOAT, ADD_DOUBLE); + } + + /** + * Returns the appropriate {@code sub} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opSub(TypeList types) { + return pickBinaryOp(types, SUB_CONST_INT, SUB_CONST_LONG, + SUB_CONST_FLOAT, SUB_CONST_DOUBLE, SUB_INT, + SUB_LONG, SUB_FLOAT, SUB_DOUBLE); + } + + /** + * Returns the appropriate {@code mul} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMul(TypeList types) { + return pickBinaryOp(types, MUL_CONST_INT, MUL_CONST_LONG, + MUL_CONST_FLOAT, MUL_CONST_DOUBLE, MUL_INT, + MUL_LONG, MUL_FLOAT, MUL_DOUBLE); + } + + /** + * Returns the appropriate {@code div} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opDiv(TypeList types) { + return pickBinaryOp(types, DIV_CONST_INT, DIV_CONST_LONG, + DIV_CONST_FLOAT, DIV_CONST_DOUBLE, DIV_INT, + DIV_LONG, DIV_FLOAT, DIV_DOUBLE); + } + + /** + * Returns the appropriate {@code rem} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opRem(TypeList types) { + return pickBinaryOp(types, REM_CONST_INT, REM_CONST_LONG, + REM_CONST_FLOAT, REM_CONST_DOUBLE, REM_INT, + REM_LONG, REM_FLOAT, REM_DOUBLE); + } + + /** + * Returns the appropriate {@code and} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAnd(TypeList types) { + return pickBinaryOp(types, AND_CONST_INT, AND_CONST_LONG, null, null, + AND_INT, AND_LONG, null, null); + } + + /** + * Returns the appropriate {@code or} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opOr(TypeList types) { + return pickBinaryOp(types, OR_CONST_INT, OR_CONST_LONG, null, null, + OR_INT, OR_LONG, null, null); + } + + /** + * Returns the appropriate {@code xor} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opXor(TypeList types) { + return pickBinaryOp(types, XOR_CONST_INT, XOR_CONST_LONG, null, null, + XOR_INT, XOR_LONG, null, null); + } + + /** + * Returns the appropriate {@code shl} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opShl(TypeList types) { + return pickBinaryOp(types, SHL_CONST_INT, SHL_CONST_LONG, null, null, + SHL_INT, SHL_LONG, null, null); + } + + /** + * Returns the appropriate {@code shr} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opShr(TypeList types) { + return pickBinaryOp(types, SHR_CONST_INT, SHR_CONST_LONG, null, null, + SHR_INT, SHR_LONG, null, null); + } + + /** + * Returns the appropriate {@code ushr} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opUshr(TypeList types) { + return pickBinaryOp(types, USHR_CONST_INT, USHR_CONST_LONG, null, null, + USHR_INT, USHR_LONG, null, null); + } + + /** + * Returns the appropriate binary arithmetic rop for the given type + * and arguments. The result is a shared instance. + * + * @param types {@code non-null;} sources of the operation + * @param int1 {@code non-null;} the int-to-constant rop + * @param long1 {@code non-null;} the long-to-constant rop + * @param float1 {@code null-ok;} the float-to-constant rop, if any + * @param double1 {@code null-ok;} the double-to-constant rop, if any + * @param int2 {@code non-null;} the int-to-int rop + * @param long2 {@code non-null;} the long-to-long or long-to-int rop + * @param float2 {@code null-ok;} the float-to-float rop, if any + * @param double2 {@code null-ok;} the double-to-double rop, if any + * @return {@code non-null;} an appropriate instance + */ + private static Rop pickBinaryOp(TypeList types, Rop int1, Rop long1, + Rop float1, Rop double1, Rop int2, + Rop long2, Rop float2, Rop double2) { + int bt1 = types.getType(0).getBasicFrameType(); + Rop result = null; + + switch (types.size()) { + case 1: { + switch(bt1) { + case Type.BT_INT: return int1; + case Type.BT_LONG: return long1; + case Type.BT_FLOAT: result = float1; break; + case Type.BT_DOUBLE: result = double1; break; + } + break; + } + case 2: { + switch(bt1) { + case Type.BT_INT: return int2; + case Type.BT_LONG: return long2; + case Type.BT_FLOAT: result = float2; break; + case Type.BT_DOUBLE: result = double2; break; + } + break; + } + } + + if (result == null) { + return throwBadTypes(types); + } + + return result; + } + + /** + * Returns the appropriate {@code neg} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being operated on + * @return {@code non-null;} an appropriate instance + */ + public static Rop opNeg(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return NEG_INT; + case Type.BT_LONG: return NEG_LONG; + case Type.BT_FLOAT: return NEG_FLOAT; + case Type.BT_DOUBLE: return NEG_DOUBLE; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code not} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being operated on + * @return {@code non-null;} an appropriate instance + */ + public static Rop opNot(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return NOT_INT; + case Type.BT_LONG: return NOT_LONG; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code cmpl} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being compared + * @return {@code non-null;} an appropriate instance + */ + public static Rop opCmpl(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_LONG: return CMPL_LONG; + case Type.BT_FLOAT: return CMPL_FLOAT; + case Type.BT_DOUBLE: return CMPL_DOUBLE; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code cmpg} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being compared + * @return {@code non-null;} an appropriate instance + */ + public static Rop opCmpg(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_FLOAT: return CMPG_FLOAT; + case Type.BT_DOUBLE: return CMPG_DOUBLE; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code conv} rop for the given types. The + * result is a shared instance. + * + * @param dest {@code non-null;} target value type + * @param source {@code non-null;} source value type + * @return {@code non-null;} an appropriate instance + */ + public static Rop opConv(TypeBearer dest, TypeBearer source) { + int dbt = dest.getBasicFrameType(); + switch (source.getBasicFrameType()) { + case Type.BT_INT: { + switch (dbt) { + case Type.BT_LONG: return CONV_I2L; + case Type.BT_FLOAT: return CONV_I2F; + case Type.BT_DOUBLE: return CONV_I2D; + default: break; + } + } + case Type.BT_LONG: { + switch (dbt) { + case Type.BT_INT: return CONV_L2I; + case Type.BT_FLOAT: return CONV_L2F; + case Type.BT_DOUBLE: return CONV_L2D; + default: break; + } + } + case Type.BT_FLOAT: { + switch (dbt) { + case Type.BT_INT: return CONV_F2I; + case Type.BT_LONG: return CONV_F2L; + case Type.BT_DOUBLE: return CONV_F2D; + default: break; + } + } + case Type.BT_DOUBLE: { + switch (dbt) { + case Type.BT_INT: return CONV_D2I; + case Type.BT_LONG: return CONV_D2L; + case Type.BT_FLOAT: return CONV_D2F; + default: break; + } + } + } + + return throwBadTypes(StdTypeList.make(dest.getType(), + source.getType())); + } + + /** + * Returns the appropriate {@code return} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being returned + * @return {@code non-null;} an appropriate instance + */ + public static Rop opReturn(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return RETURN_INT; + case Type.BT_LONG: return RETURN_LONG; + case Type.BT_FLOAT: return RETURN_FLOAT; + case Type.BT_DOUBLE: return RETURN_DOUBLE; + case Type.BT_OBJECT: return RETURN_OBJECT; + case Type.BT_VOID: return RETURN_VOID; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code aget} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} element type of array being accessed + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAget(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return AGET_INT; + case Type.BT_LONG: return AGET_LONG; + case Type.BT_FLOAT: return AGET_FLOAT; + case Type.BT_DOUBLE: return AGET_DOUBLE; + case Type.BT_OBJECT: return AGET_OBJECT; + case Type.BT_BOOLEAN: return AGET_BOOLEAN; + case Type.BT_BYTE: return AGET_BYTE; + case Type.BT_CHAR: return AGET_CHAR; + case Type.BT_SHORT: return AGET_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code aput} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} element type of array being accessed + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAput(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return APUT_INT; + case Type.BT_LONG: return APUT_LONG; + case Type.BT_FLOAT: return APUT_FLOAT; + case Type.BT_DOUBLE: return APUT_DOUBLE; + case Type.BT_OBJECT: return APUT_OBJECT; + case Type.BT_BOOLEAN: return APUT_BOOLEAN; + case Type.BT_BYTE: return APUT_BYTE; + case Type.BT_CHAR: return APUT_CHAR; + case Type.BT_SHORT: return APUT_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code new-array} rop for the given + * type. The result is a shared instance. + * + * @param arrayType {@code non-null;} array type of array being created + * @return {@code non-null;} an appropriate instance + */ + public static Rop opNewArray(TypeBearer arrayType) { + Type type = arrayType.getType(); + Type elementType = type.getComponentType(); + + switch (elementType.getBasicType()) { + case Type.BT_INT: return NEW_ARRAY_INT; + case Type.BT_LONG: return NEW_ARRAY_LONG; + case Type.BT_FLOAT: return NEW_ARRAY_FLOAT; + case Type.BT_DOUBLE: return NEW_ARRAY_DOUBLE; + case Type.BT_BOOLEAN: return NEW_ARRAY_BOOLEAN; + case Type.BT_BYTE: return NEW_ARRAY_BYTE; + case Type.BT_CHAR: return NEW_ARRAY_CHAR; + case Type.BT_SHORT: return NEW_ARRAY_SHORT; + case Type.BT_OBJECT: { + return new Rop(RegOps.NEW_ARRAY, type, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-object"); + } + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code filled-new-array} rop for the given + * type. The result may be a shared instance. + * + * @param arrayType {@code non-null;} type of array being created + * @param count {@code count >= 0;} number of elements that the array should have + * @return {@code non-null;} an appropriate instance + */ + public static Rop opFilledNewArray(TypeBearer arrayType, int count) { + Type type = arrayType.getType(); + Type elementType = type.getComponentType(); + + if (elementType.isCategory2()) { + return throwBadType(arrayType); + } + + if (count < 0) { + throw new IllegalArgumentException("count < 0"); + } + + StdTypeList sourceTypes = new StdTypeList(count); + + for (int i = 0; i < count; i++) { + sourceTypes.set(i, elementType); + } + + // Note: The resulting rop is considered call-like. + return new Rop(RegOps.FILLED_NEW_ARRAY, + sourceTypes, + Exceptions.LIST_Error); + } + + /** + * Returns the appropriate {@code get-field} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opGetField(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return GET_FIELD_INT; + case Type.BT_LONG: return GET_FIELD_LONG; + case Type.BT_FLOAT: return GET_FIELD_FLOAT; + case Type.BT_DOUBLE: return GET_FIELD_DOUBLE; + case Type.BT_OBJECT: return GET_FIELD_OBJECT; + case Type.BT_BOOLEAN: return GET_FIELD_BOOLEAN; + case Type.BT_BYTE: return GET_FIELD_BYTE; + case Type.BT_CHAR: return GET_FIELD_CHAR; + case Type.BT_SHORT: return GET_FIELD_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code put-field} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opPutField(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return PUT_FIELD_INT; + case Type.BT_LONG: return PUT_FIELD_LONG; + case Type.BT_FLOAT: return PUT_FIELD_FLOAT; + case Type.BT_DOUBLE: return PUT_FIELD_DOUBLE; + case Type.BT_OBJECT: return PUT_FIELD_OBJECT; + case Type.BT_BOOLEAN: return PUT_FIELD_BOOLEAN; + case Type.BT_BYTE: return PUT_FIELD_BYTE; + case Type.BT_CHAR: return PUT_FIELD_CHAR; + case Type.BT_SHORT: return PUT_FIELD_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code get-static} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opGetStatic(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return GET_STATIC_INT; + case Type.BT_LONG: return GET_STATIC_LONG; + case Type.BT_FLOAT: return GET_STATIC_FLOAT; + case Type.BT_DOUBLE: return GET_STATIC_DOUBLE; + case Type.BT_OBJECT: return GET_STATIC_OBJECT; + case Type.BT_BOOLEAN: return GET_STATIC_BOOLEAN; + case Type.BT_BYTE: return GET_STATIC_BYTE; + case Type.BT_CHAR: return GET_STATIC_CHAR; + case Type.BT_SHORT: return GET_STATIC_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code put-static} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opPutStatic(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return PUT_STATIC_INT; + case Type.BT_LONG: return PUT_STATIC_LONG; + case Type.BT_FLOAT: return PUT_STATIC_FLOAT; + case Type.BT_DOUBLE: return PUT_STATIC_DOUBLE; + case Type.BT_OBJECT: return PUT_STATIC_OBJECT; + case Type.BT_BOOLEAN: return PUT_STATIC_BOOLEAN; + case Type.BT_BYTE: return PUT_STATIC_BYTE; + case Type.BT_CHAR: return PUT_STATIC_CHAR; + case Type.BT_SHORT: return PUT_STATIC_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code invoke-static} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeStatic(Prototype meth) { + return new Rop(RegOps.INVOKE_STATIC, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-virtual} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeVirtual(Prototype meth) { + return new Rop(RegOps.INVOKE_VIRTUAL, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-super} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeSuper(Prototype meth) { + return new Rop(RegOps.INVOKE_SUPER, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-direct} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeDirect(Prototype meth) { + return new Rop(RegOps.INVOKE_DIRECT, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-interface} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeInterface(Prototype meth) { + return new Rop(RegOps.INVOKE_INTERFACE, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-polymorphic} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokePolymorphic(Prototype meth) { + return new Rop(RegOps.INVOKE_POLYMORPHIC, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code mark-local} rop for the given type. + * The result is a shared instance. + * + * @param type {@code non-null;} type of value being marked + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMarkLocal(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return MARK_LOCAL_INT; + case Type.BT_LONG: return MARK_LOCAL_LONG; + case Type.BT_FLOAT: return MARK_LOCAL_FLOAT; + case Type.BT_DOUBLE: return MARK_LOCAL_DOUBLE; + case Type.BT_OBJECT: return MARK_LOCAL_OBJECT; + } + + return throwBadType(type); + } + + /** + * This class is uninstantiable. + */ + private Rops() { + // This space intentionally left blank. + } + + /** + * Throws the right exception to complain about a bogus type. + * + * @param type {@code non-null;} the bad type + * @return never + */ + private static Rop throwBadType(TypeBearer type) { + throw new IllegalArgumentException("bad type: " + type); + } + + /** + * Throws the right exception to complain about a bogus list of types. + * + * @param types {@code non-null;} the bad types + * @return never + */ + private static Rop throwBadTypes(TypeList types) { + throw new IllegalArgumentException("bad types: " + types); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/SourcePosition.java b/dexlib/src/main/java/com/android/dx/rop/code/SourcePosition.java new file mode 100644 index 000000000..11ac99250 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/SourcePosition.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.CstString; +import com.android.dx.util.Hex; + +/** + * Information about a source position for code, which includes both a + * line number and original bytecode address. + */ +public final class SourcePosition { + /** {@code non-null;} convenient "no information known" instance */ + public static final SourcePosition NO_INFO = + new SourcePosition(null, -1, -1); + + /** {@code null-ok;} name of the file of origin or {@code null} if unknown */ + private final CstString sourceFile; + + /** + * {@code >= -1;} the bytecode address, or {@code -1} if that + * information is unknown + */ + private final int address; + + /** + * {@code >= -1;} the line number, or {@code -1} if that + * information is unknown + */ + private final int line; + + /** + * Constructs an instance. + * + * @param sourceFile {@code null-ok;} name of the file of origin or + * {@code null} if unknown + * @param address {@code >= -1;} original bytecode address or {@code -1} + * if unknown + * @param line {@code >= -1;} original line number or {@code -1} if + * unknown + */ + public SourcePosition(CstString sourceFile, int address, int line) { + if (address < -1) { + throw new IllegalArgumentException("address < -1"); + } + + if (line < -1) { + throw new IllegalArgumentException("line < -1"); + } + + this.sourceFile = sourceFile; + this.address = address; + this.line = line; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(50); + + if (sourceFile != null) { + sb.append(sourceFile.toHuman()); + sb.append(":"); + } + + if (line >= 0) { + sb.append(line); + } + + sb.append('@'); + + if (address < 0) { + sb.append("????"); + } else { + sb.append(Hex.u2(address)); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof SourcePosition)) { + return false; + } + + if (this == other) { + return true; + } + + SourcePosition pos = (SourcePosition) other; + + return (address == pos.address) && sameLineAndFile(pos); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return sourceFile.hashCode() + address + line; + } + + /** + * Returns whether the lines match between this instance and + * the one given. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code true} iff the lines match + */ + public boolean sameLine(SourcePosition other) { + return (line == other.line); + } + + /** + * Returns whether the lines and files match between this instance and + * the one given. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code true} iff the lines and files match + */ + public boolean sameLineAndFile(SourcePosition other) { + return (line == other.line) && + ((sourceFile == other.sourceFile) || + ((sourceFile != null) && sourceFile.equals(other.sourceFile))); + } + + /** + * Gets the source file, if known. + * + * @return {@code null-ok;} the source file or {@code null} if unknown + */ + public CstString getSourceFile() { + return sourceFile; + } + + /** + * Gets the original bytecode address. + * + * @return {@code >= -1;} the address or {@code -1} if unknown + */ + public int getAddress() { + return address; + } + + /** + * Gets the original line number. + * + * @return {@code >= -1;} the original line number or {@code -1} if + * unknown + */ + public int getLine() { + return line; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/SwitchInsn.java b/dexlib/src/main/java/com/android/dx/rop/code/SwitchInsn.java new file mode 100644 index 000000000..31bb94dbb --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/SwitchInsn.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; +import com.android.dx.util.IntList; + +/** + * Instruction which contains switch cases. + */ +public final class SwitchInsn + extends Insn { + /** {@code non-null;} list of switch cases */ + private final IntList cases; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + * @param cases {@code non-null;} list of switch cases + */ + public SwitchInsn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpecList sources, IntList cases) { + super(opcode, position, result, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_SWITCH) { + throw new IllegalArgumentException("bogus branchingness"); + } + + if (cases == null) { + throw new NullPointerException("cases == null"); + } + + this.cases = cases; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return cases.toString(); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitSwitchInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new SwitchInsn(getOpcode(), getPosition(), + getResult().withOffset(delta), + getSources().withOffset(delta), + cases); + } + + /** + * {@inheritDoc} + * + *

SwitchInsn always compares false. The current use for this method + * never encounters {@code SwitchInsn}s + */ + @Override + public boolean contentEquals(Insn b) { + return false; + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new SwitchInsn(getOpcode(), getPosition(), + result, + sources, + cases); + } + + /** + * Gets the list of switch cases. + * + * @return {@code non-null;} the case list + */ + public IntList getCases() { + return cases; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/ThrowingCstInsn.java b/dexlib/src/main/java/com/android/dx/rop/code/ThrowingCstInsn.java new file mode 100644 index 000000000..0cf321c21 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/ThrowingCstInsn.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; + +/** + * Instruction which contains an explicit reference to a constant + * and which might throw an exception. + */ +public final class ThrowingCstInsn + extends CstInsn { + /** {@code non-null;} list of exceptions caught */ + private final TypeList catches; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param catches {@code non-null;} list of exceptions caught + * @param cst {@code non-null;} the constant + */ + public ThrowingCstInsn(Rop opcode, SourcePosition position, + RegisterSpecList sources, + TypeList catches, Constant cst) { + super(opcode, position, null, sources, cst); + + if (opcode.getBranchingness() != Rop.BRANCH_THROW) { + throw new IllegalArgumentException("opcode with invalid branchingness: " + opcode.getBranchingness()); + } + + if (catches == null) { + throw new NullPointerException("catches == null"); + } + + this.catches = catches; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + Constant cst = getConstant(); + String constantString = cst.toHuman(); + if (cst instanceof CstString) { + constantString = ((CstString) cst).toQuoted(); + } + return constantString + " " + ThrowingInsn.toCatchString(catches); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return catches; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitThrowingCstInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + return new ThrowingCstInsn(getOpcode(), getPosition(), + getSources(), catches.withAddedType(type), + getConstant()); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new ThrowingCstInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + catches, + getConstant()); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new ThrowingCstInsn(getOpcode(), getPosition(), + sources, + catches, + getConstant()); + } + + +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/ThrowingInsn.java b/dexlib/src/main/java/com/android/dx/rop/code/ThrowingInsn.java new file mode 100644 index 000000000..cb3f67935 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/ThrowingInsn.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeList; + +/** + * Instruction which possibly throws. The {@code successors} list in the + * basic block an instance of this class is inside corresponds in-order to + * the list of exceptions handled by this instruction, with the + * no-exception case appended as the final target. + */ +public final class ThrowingInsn + extends Insn { + /** {@code non-null;} list of exceptions caught */ + private final TypeList catches; + + /** + * Gets the string form of a register spec list to be used as a catches + * list. + * + * @param catches {@code non-null;} the catches list + * @return {@code non-null;} the string form + */ + public static String toCatchString(TypeList catches) { + StringBuffer sb = new StringBuffer(100); + + sb.append("catch"); + + int sz = catches.size(); + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(catches.getType(i).toHuman()); + } + + return sb.toString(); + } + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param catches {@code non-null;} list of exceptions caught + */ + public ThrowingInsn(Rop opcode, SourcePosition position, + RegisterSpecList sources, + TypeList catches) { + super(opcode, position, null, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_THROW) { + throw new IllegalArgumentException("opcode with invalid branchingness: " + opcode.getBranchingness()); + } + + if (catches == null) { + throw new NullPointerException("catches == null"); + } + + this.catches = catches; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return toCatchString(catches); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return catches; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitThrowingInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + return new ThrowingInsn(getOpcode(), getPosition(), + getSources(), catches.withAddedType(type)); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new ThrowingInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + catches); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new ThrowingInsn(getOpcode(), getPosition(), + sources, + catches); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/TranslationAdvice.java b/dexlib/src/main/java/com/android/dx/rop/code/TranslationAdvice.java new file mode 100644 index 000000000..832d84df6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/TranslationAdvice.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.code; + +/** + * Interface for "advice" passed from the late stage of translation back + * to the early stage. This allows for the final target architecture to + * exert its influence early in the translation process without having + * the early stage code be explicitly tied to the target. + */ +public interface TranslationAdvice { + /** + * Returns an indication of whether the target can directly represent an + * instruction with the given opcode operating on the given arguments, + * where the last source argument is used as a constant. (That is, the + * last argument must have a type which indicates it is a known constant.) + * The instruction associated must have exactly two sources. + * + * @param opcode {@code non-null;} the opcode + * @param sourceA {@code non-null;} the first source + * @param sourceB {@code non-null;} the second source + * @return {@code true} iff the target can represent the operation + * using a constant for the last argument + */ + public boolean hasConstantOperation(Rop opcode, + RegisterSpec sourceA, RegisterSpec sourceB); + + /** + * Returns true if the translation target requires the sources of the + * specified opcode to be in order and contiguous (eg, for an invoke-range) + * + * @param opcode {@code non-null;} opcode + * @param sources {@code non-null;} source list + * @return {@code true} iff the target requires the sources to be + * in order and contiguous. + */ + public boolean requiresSourcesInOrder(Rop opcode, RegisterSpecList sources); + + /** + * Gets the maximum register width that can be represented optimally. + * For example, Dex bytecode does not have instruction forms that take + * register numbers larger than 15 for all instructions so + * DexTranslationAdvice returns 15 here. + * + * @return register count noted above + */ + public int getMaxOptimalRegisterCount(); +} diff --git a/dexlib/src/main/java/com/android/dx/rop/code/package.html b/dexlib/src/main/java/com/android/dx/rop/code/package.html new file mode 100644 index 000000000..86566b497 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/code/package.html @@ -0,0 +1,8 @@ + +

Classes relating to a register-based opcode system.

+ +

PACKAGES USED: +

    +
  • com.android.dx.util
  • +
+ diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/Constant.java b/dexlib/src/main/java/com/android/dx/rop/cst/Constant.java new file mode 100644 index 000000000..3ef035e01 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/Constant.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.util.ToHuman; + +/** + * Base class for constants of all sorts. + */ +public abstract class Constant + implements ToHuman, Comparable { + /** + * Returns {@code true} if this instance is a category-2 constant, + * meaning it takes up two slots in the constant pool, or + * {@code false} if this instance is category-1. + * + * @return {@code true} iff this instance is category-2 + */ + public abstract boolean isCategory2(); + + /** + * Returns the human name for the particular type of constant + * this instance is. + * + * @return {@code non-null;} the name + */ + public abstract String typeName(); + + /** + * {@inheritDoc} + * + * This compares in class-major and value-minor order. + */ + public final int compareTo(Constant other) { + Class clazz = getClass(); + Class otherClazz = other.getClass(); + + if (clazz != otherClazz) { + return clazz.getName().compareTo(otherClazz.getName()); + } + + return compareTo0(other); + } + + /** + * Compare the values of this and another instance, which are guaranteed + * to be of the same class. Subclasses must implement this. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code -1}, {@code 0}, or {@code 1}, as usual + * for a comparison + */ + protected abstract int compareTo0(Constant other); +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/ConstantPool.java b/dexlib/src/main/java/com/android/dx/rop/cst/ConstantPool.java new file mode 100644 index 000000000..f4b808654 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/ConstantPool.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +/** + * Interface for constant pools, which are, more or less, just lists of + * {@link Constant} objects. + */ +public interface ConstantPool { + /** + * Get the "size" of the constant pool. This corresponds to the + * class file field {@code constant_pool_count}, and is in fact + * always at least one more than the actual size of the constant pool, + * as element {@code 0} is always invalid. + * + * @return {@code >= 1;} the size + */ + public int size(); + + /** + * Get the {@code n}th entry in the constant pool, which must + * be valid. + * + * @param n {@code n >= 0, n < size();} the constant pool index + * @return {@code non-null;} the corresponding entry + * @throws IllegalArgumentException thrown if {@code n} is + * in-range but invalid + */ + public Constant get(int n); + + /** + * Get the {@code n}th entry in the constant pool, which must + * be valid unless {@code n == 0}, in which case {@code null} + * is returned. + * + * @param n {@code n >= 0, n < size();} the constant pool index + * @return {@code null-ok;} the corresponding entry, if {@code n != 0} + * @throws IllegalArgumentException thrown if {@code n} is + * in-range and non-zero but invalid + */ + public Constant get0Ok(int n); + + /** + * Get the {@code n}th entry in the constant pool, or + * {@code null} if the index is in-range but invalid. In + * particular, {@code null} is returned for index {@code 0} + * as well as the index after any entry which is defined to take up + * two slots (that is, {@code Long} and {@code Double} + * entries). + * + * @param n {@code n >= 0, n < size();} the constant pool index + * @return {@code null-ok;} the corresponding entry, or {@code null} if + * the index is in-range but invalid + */ + public Constant getOrNull(int n); + + /** + * Get all entries in this constant pool. + * + * @return the returned array may contain null entries. + */ + public Constant[] getEntries(); +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstAnnotation.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstAnnotation.java new file mode 100644 index 000000000..8cdf1df44 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstAnnotation.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.annotation.Annotation; + +/** + * Constant type that represents an annotation. + */ +public final class CstAnnotation extends Constant { + /** {@code non-null;} the actual annotation */ + private final Annotation annotation; + + /** + * Constructs an instance. + * + * @param annotation {@code non-null;} the annotation to hold + */ + public CstAnnotation(Annotation annotation) { + if (annotation == null) { + throw new NullPointerException("annotation == null"); + } + + annotation.throwIfMutable(); + + this.annotation = annotation; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof CstAnnotation)) { + return false; + } + + return annotation.equals(((CstAnnotation) other).annotation); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotation.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return annotation.compareTo(((CstAnnotation) other).annotation); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return annotation.toString(); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "annotation"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return annotation.toString(); + } + + /** + * Get the underlying annotation. + * + * @return {@code non-null;} the annotation + */ + public Annotation getAnnotation() { + return annotation; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstArray.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstArray.java new file mode 100644 index 000000000..5644b2fe0 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstArray.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.util.FixedSizeList; + +/** + * Constant type to represent a fixed array of other constants. + */ +public final class CstArray extends Constant { + /** {@code non-null;} the actual list of contents */ + private final List list; + + /** + * Constructs an instance. + * + * @param list {@code non-null;} the actual list of contents + */ + public CstArray(List list) { + if (list == null) { + throw new NullPointerException("list == null"); + } + + list.throwIfMutable(); + + this.list = list; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof CstArray)) { + return false; + } + + return list.equals(((CstArray) other).list); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return list.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return list.compareTo(((CstArray) other).list); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return list.toString("array{", ", ", "}"); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "array"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return list.toHuman("{", ", ", "}"); + } + + /** + * Get the underlying list. + * + * @return {@code non-null;} the list + */ + public List getList() { + return list; + } + + /** + * List of {@link Constant} instances. + */ + public static final class List + extends FixedSizeList implements Comparable { + /** + * Constructs an instance. All indices initially contain + * {@code null}. + * + * @param size the size of the list + */ + public List(int size) { + super(size); + } + + /** {@inheritDoc} */ + public int compareTo(List other) { + int thisSize = size(); + int otherSize = other.size(); + int compareSize = (thisSize < otherSize) ? thisSize : otherSize; + + for (int i = 0; i < compareSize; i++) { + Constant thisItem = (Constant) get0(i); + Constant otherItem = (Constant) other.get0(i); + int compare = thisItem.compareTo(otherItem); + if (compare != 0) { + return compare; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } + + return 0; + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Constant get(int n) { + return (Constant) get0(n); + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param a {@code null-ok;} the element to set at {@code n} + */ + public void set(int n, Constant a) { + set0(n, a); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstBaseMethodRef.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstBaseMethodRef.java new file mode 100644 index 000000000..e7722311e --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstBaseMethodRef.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Prototype; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; + +/** + * Base class for constants of "methodish" type. + * + *

Note: As a {@link TypeBearer}, this class bears the return type + * of the method.

+ */ +public abstract class CstBaseMethodRef + extends CstMemberRef { + /** {@code non-null;} the raw prototype for this method */ + private final Prototype prototype; + + /** + * {@code null-ok;} the prototype for this method taken to be an instance + * method, or {@code null} if not yet calculated + */ + private Prototype instancePrototype; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + /*package*/ CstBaseMethodRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + + String descriptor = getNat().getDescriptor().getString(); + if (isSignaturePolymorphic()) { + // The prototype for signature polymorphic methods is used to + // construct call-site information and select the true invocation + // target (invoke() or invokeExact(). The prototype is created + // without being interned to avoid polluting the DEX file with + // unused data. + this.prototype = Prototype.fromDescriptor(descriptor); + } else { + this.prototype = Prototype.intern(descriptor); + } + this.instancePrototype = null; + } + + /** + * Gets the raw prototype of this method. This doesn't include a + * {@code this} argument. + * + * @return {@code non-null;} the method prototype + */ + public final Prototype getPrototype() { + return prototype; + } + + /** + * Gets the prototype of this method as either a + * {@code static} or instance method. In the case of a + * {@code static} method, this is the same as the raw + * prototype. In the case of an instance method, this has an + * appropriately-typed {@code this} argument as the first + * one. + * + * @param isStatic whether the method should be considered static + * @return {@code non-null;} the method prototype + */ + public final Prototype getPrototype(boolean isStatic) { + if (isStatic) { + return prototype; + } else { + if (instancePrototype == null) { + Type thisType = getDefiningClass().getClassType(); + instancePrototype = prototype.withFirstParameter(thisType); + } + return instancePrototype; + } + } + + /** {@inheritDoc} */ + @Override + protected final int compareTo0(Constant other) { + int cmp = super.compareTo0(other); + + if (cmp != 0) { + return cmp; + } + + CstBaseMethodRef otherMethod = (CstBaseMethodRef) other; + return prototype.compareTo(otherMethod.prototype); + } + + /** + * {@inheritDoc} + * + * In this case, this method returns the return type of this method. + * + * @return {@code non-null;} the method's return type + */ + public final Type getType() { + return prototype.getReturnType(); + } + + /** + * Gets the number of words of parameters required by this + * method's descriptor. Since instances of this class have no way + * to know if they will be used in a {@code static} or + * instance context, one has to indicate this explicitly as an + * argument. This method is just a convenient shorthand for + * {@code getPrototype().getParameterTypes().getWordCount()}, + * plus {@code 1} if the method is to be treated as an + * instance method. + * + * @param isStatic whether the method should be considered static + * @return {@code >= 0;} the argument word count + */ + public final int getParameterWordCount(boolean isStatic) { + return getPrototype(isStatic).getParameterTypes().getWordCount(); + } + + /** + * Gets whether this is a reference to an instance initialization + * method. This is just a convenient shorthand for + * {@code getNat().isInstanceInit()}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isInstanceInit() { + return getNat().isInstanceInit(); + } + + /** + * Gets whether this is a reference to a class initialization + * method. This is just a convenient shorthand for + * {@code getNat().isClassInit()}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isClassInit() { + return getNat().isClassInit(); + } + + /** + * Get whether this is a reference to a signature polymorphic + * method. This means it is defined in {@code java.lang.invoke.MethodHandle} and + * is either the {@code invoke} or the {@code invokeExact} method. + * + * @return {@code true} iff this is a reference to a + * signature polymorphic method. + */ + public final boolean isSignaturePolymorphic() { + return (getDefiningClass().equals(CstType.METHOD_HANDLE) && + getNat().isSignaturePolymorphic()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstBoolean.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstBoolean.java new file mode 100644 index 000000000..5ff858a5a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstBoolean.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; + +/** + * Constants of type {@code boolean}. + */ +public final class CstBoolean + extends CstLiteral32 { + /** {@code non-null;} instance representing {@code false} */ + public static final CstBoolean VALUE_FALSE = new CstBoolean(false); + + /** {@code non-null;} instance representing {@code true} */ + public static final CstBoolean VALUE_TRUE = new CstBoolean(true); + + /** + * Makes an instance for the given value. This will return an + * already-allocated instance. + * + * @param value the {@code boolean} value + * @return {@code non-null;} the appropriate instance + */ + public static CstBoolean make(boolean value) { + return value ? VALUE_TRUE : VALUE_FALSE; + } + + /** + * Makes an instance for the given {@code int} value. This + * will return an already-allocated instance. + * + * @param value must be either {@code 0} or {@code 1} + * @return {@code non-null;} the appropriate instance + */ + public static CstBoolean make(int value) { + if (value == 0) { + return VALUE_FALSE; + } else if (value == 1) { + return VALUE_TRUE; + } else { + throw new IllegalArgumentException("bogus value: " + value); + } + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code boolean} value + */ + private CstBoolean(boolean value) { + super(value ? 1 : 0); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return getValue() ? "boolean{true}" : "boolean{false}"; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.BOOLEAN; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "boolean"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return getValue() ? "true" : "false"; + } + + /** + * Gets the {@code boolean} value. + * + * @return the value + */ + public boolean getValue() { + return (getIntBits() == 0) ? false : true; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstByte.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstByte.java new file mode 100644 index 000000000..fc8f58f8c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstByte.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; +import com.android.dx.util.Hex; + +/** + * Constants of type {@code byte}. + */ +public final class CstByte + extends CstLiteral32 { + /** {@code non-null;} the value {@code 0} as an instance of this class */ + public static final CstByte VALUE_0 = make((byte) 0); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code byte} value + */ + public static CstByte make(byte value) { + return new CstByte(value); + } + + /** + * Makes an instance for the given {@code int} value. This + * may (but does not necessarily) return an already-allocated + * instance. + * + * @param value the value, which must be in range for a {@code byte} + * @return {@code non-null;} the appropriate instance + */ + public static CstByte make(int value) { + byte cast = (byte) value; + + if (cast != value) { + throw new IllegalArgumentException("bogus byte value: " + + value); + } + + return make(cast); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code byte} value + */ + private CstByte(byte value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "byte{0x" + Hex.u1(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.BYTE; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "byte"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code byte} value. + * + * @return the value + */ + public byte getValue() { + return (byte) getIntBits(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstChar.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstChar.java new file mode 100644 index 000000000..21d8b6784 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstChar.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; +import com.android.dx.util.Hex; + +/** + * Constants of type {@code char}. + */ +public final class CstChar + extends CstLiteral32 { + /** {@code non-null;} the value {@code 0} as an instance of this class */ + public static final CstChar VALUE_0 = make((char) 0); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code char} value + */ + public static CstChar make(char value) { + return new CstChar(value); + } + + /** + * Makes an instance for the given {@code int} value. This + * may (but does not necessarily) return an already-allocated + * instance. + * + * @param value the value, which must be in range for a {@code char} + * @return {@code non-null;} the appropriate instance + */ + public static CstChar make(int value) { + char cast = (char) value; + + if (cast != value) { + throw new IllegalArgumentException("bogus char value: " + + value); + } + + return make(cast); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code char} value + */ + private CstChar(char value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "char{0x" + Hex.u2(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.CHAR; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "char"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code char} value. + * + * @return the value + */ + public char getValue() { + return (char) getIntBits(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstDouble.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstDouble.java new file mode 100644 index 000000000..8f1766fca --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstDouble.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; +import com.android.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Double_info}. + */ +public final class CstDouble + extends CstLiteral64 { + /** {@code non-null;} instance representing {@code 0} */ + public static final CstDouble VALUE_0 = + new CstDouble(Double.doubleToLongBits(0.0)); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstDouble VALUE_1 = + new CstDouble(Double.doubleToLongBits(1.0)); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param bits the {@code double} value as {@code long} bits + */ + public static CstDouble make(long bits) { + /* + * Note: Javadoc notwithstanding, this implementation always + * allocates. + */ + return new CstDouble(bits); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param bits the {@code double} value as {@code long} bits + */ + private CstDouble(long bits) { + super(bits); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + long bits = getLongBits(); + return "double{0x" + Hex.u8(bits) + " / " + + Double.longBitsToDouble(bits) + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.DOUBLE; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "double"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Double.toString(Double.longBitsToDouble(getLongBits())); + } + + /** + * Gets the {@code double} value. + * + * @return the value + */ + public double getValue() { + return Double.longBitsToDouble(getLongBits()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstEnumRef.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstEnumRef.java new file mode 100644 index 000000000..641ab3f4d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstEnumRef.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; + +/** + * Constant type to represent a reference to a particular constant + * value of an enumerated type. + */ +public final class CstEnumRef extends CstMemberRef { + /** {@code null-ok;} the corresponding field ref, lazily initialized */ + private CstFieldRef fieldRef; + + /** + * Constructs an instance. + * + * @param nat {@code non-null;} the name-and-type; the defining class is derived + * from this + */ + public CstEnumRef(CstNat nat) { + super(new CstType(nat.getFieldType()), nat); + + fieldRef = null; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "enum"; + } + + /** + * {@inheritDoc} + * + * Note: This returns the enumerated type. + */ + public Type getType() { + return getDefiningClass().getClassType(); + } + + /** + * Get a {@link CstFieldRef} that corresponds with this instance. + * + * @return {@code non-null;} the corresponding field reference + */ + public CstFieldRef getFieldRef() { + if (fieldRef == null) { + fieldRef = new CstFieldRef(getDefiningClass(), getNat()); + } + + return fieldRef; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstFieldRef.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstFieldRef.java new file mode 100644 index 000000000..a4d718031 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstFieldRef.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; + +/** + * Constants of type {@code CONSTANT_Fieldref_info}. + */ +public final class CstFieldRef extends CstMemberRef { + /** + * Returns an instance of this class that represents the static + * field which should hold the class corresponding to a given + * primitive type. For example, if given {@link Type#INT}, this + * method returns an instance corresponding to the field + * {@code java.lang.Integer.TYPE}. + * + * @param primitiveType {@code non-null;} the primitive type + * @return {@code non-null;} the corresponding static field + */ + public static CstFieldRef forPrimitiveType(Type primitiveType) { + return new CstFieldRef(CstType.forBoxedPrimitiveType(primitiveType), + CstNat.PRIMITIVE_TYPE_NAT); + } + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + public CstFieldRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "field"; + } + + /** + * Returns the type of this field. + * + * @return {@code non-null;} the field's type + */ + public Type getType() { + return getNat().getFieldType(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + int cmp = super.compareTo0(other); + + if (cmp != 0) { + return cmp; + } + + CstFieldRef otherField = (CstFieldRef) other; + CstString thisDescriptor = getNat().getDescriptor(); + CstString otherDescriptor = otherField.getNat().getDescriptor(); + return thisDescriptor.compareTo(otherDescriptor); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstFloat.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstFloat.java new file mode 100644 index 000000000..0a2354a4d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstFloat.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; +import com.android.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Float_info}. + */ +public final class CstFloat + extends CstLiteral32 { + /** {@code non-null;} instance representing {@code 0} */ + public static final CstFloat VALUE_0 = make(Float.floatToIntBits(0.0f)); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstFloat VALUE_1 = make(Float.floatToIntBits(1.0f)); + + /** {@code non-null;} instance representing {@code 2} */ + public static final CstFloat VALUE_2 = make(Float.floatToIntBits(2.0f)); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param bits the {@code float} value as {@code int} bits + */ + public static CstFloat make(int bits) { + /* + * Note: Javadoc notwithstanding, this implementation always + * allocates. + */ + return new CstFloat(bits); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param bits the {@code float} value as {@code int} bits + */ + private CstFloat(int bits) { + super(bits); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int bits = getIntBits(); + return "float{0x" + Hex.u4(bits) + " / " + + Float.intBitsToFloat(bits) + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.FLOAT; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "float"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Float.toString(Float.intBitsToFloat(getIntBits())); + } + + /** + * Gets the {@code float} value. + * + * @return the value + */ + public float getValue() { + return Float.intBitsToFloat(getIntBits()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstInteger.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstInteger.java new file mode 100644 index 000000000..3691fc08c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstInteger.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; +import com.android.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Integer_info}. + */ +public final class CstInteger + extends CstLiteral32 { + /** {@code non-null;} array of cached instances */ + private static final CstInteger[] cache = new CstInteger[511]; + + /** {@code non-null;} instance representing {@code -1} */ + public static final CstInteger VALUE_M1 = make(-1); + + /** {@code non-null;} instance representing {@code 0} */ + public static final CstInteger VALUE_0 = make(0); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstInteger VALUE_1 = make(1); + + /** {@code non-null;} instance representing {@code 2} */ + public static final CstInteger VALUE_2 = make(2); + + /** {@code non-null;} instance representing {@code 3} */ + public static final CstInteger VALUE_3 = make(3); + + /** {@code non-null;} instance representing {@code 4} */ + public static final CstInteger VALUE_4 = make(4); + + /** {@code non-null;} instance representing {@code 5} */ + public static final CstInteger VALUE_5 = make(5); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code int} value + * @return {@code non-null;} the appropriate instance + */ + public static CstInteger make(int value) { + /* + * Note: No need to synchronize, since we don't make any sort + * of guarantee about ==, and it's okay to overwrite existing + * entries too. + */ + int idx = (value & 0x7fffffff) % cache.length; + CstInteger obj = cache[idx]; + + if ((obj != null) && (obj.getValue() == value)) { + return obj; + } + + obj = new CstInteger(value); + cache[idx] = obj; + return obj; + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code int} value + */ + private CstInteger(int value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "int{0x" + Hex.u4(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.INT; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "int"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code int} value. + * + * @return the value + */ + public int getValue() { + return getIntBits(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstInterfaceMethodRef.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstInterfaceMethodRef.java new file mode 100644 index 000000000..8b8cb30a1 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstInterfaceMethodRef.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +/** + * Constants of type {@code CONSTANT_InterfaceMethodref_info}. + */ +public final class CstInterfaceMethodRef + extends CstBaseMethodRef { + /** + * {@code null-ok;} normal {@link CstMethodRef} that corresponds to this + * instance, if calculated + */ + private CstMethodRef methodRef; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + public CstInterfaceMethodRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + methodRef = null; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "ifaceMethod"; + } + + /** + * Gets a normal (non-interface) {@link CstMethodRef} that corresponds to + * this instance. + * + * @return {@code non-null;} an appropriate instance + */ + public CstMethodRef toMethodRef() { + if (methodRef == null) { + methodRef = new CstMethodRef(getDefiningClass(), getNat()); + } + + return methodRef; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstInvokeDynamic.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstInvokeDynamic.java new file mode 100644 index 000000000..7d4f97ab9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstInvokeDynamic.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +/** + * Constants of type {@code InvokeDynamic}. + */ +public final class CstInvokeDynamic extends Constant { + + /** The index of the bootstrap method in the bootstrap method table */ + private final int bootstrapMethodIndex; + /** {@code non-null;} the name and type */ + private final CstNat nat; + + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param bootstrapMethodIndex The index of the bootstrap method in the bootstrap method table + * @param nat the name and type + * @return {@code non-null;} the appropriate instance + */ + public static CstInvokeDynamic make(int bootstrapMethodIndex, CstNat nat) { + return new CstInvokeDynamic(bootstrapMethodIndex, nat); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param bootstrapMethodIndex The index of the bootstrap method in the bootstrap method table + * @param nat the name and type + */ + private CstInvokeDynamic(int bootstrapMethodIndex, CstNat nat) { + this.bootstrapMethodIndex = bootstrapMethodIndex; + this.nat = nat; + } + + @Override + public String toString() { + return toHuman(); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "InvokeDynamic"; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return "InvokeDynamic(" + bootstrapMethodIndex + ", " + nat.toHuman() + ")"; + } + + /** + * Gets the bootstrap method index. + * + * @return the bootstrap method index + */ + public int getBootstrapMethodIndex() { + return bootstrapMethodIndex; + } + + /** + * Gets the {@code CstNat} value. + * + * @return the name and type + */ + public CstNat getNat() { + return nat; + } + + @Override + public boolean isCategory2() { + return false; + } + + @Override + protected int compareTo0(Constant other) { + CstInvokeDynamic otherInvoke = (CstInvokeDynamic) other; + if (bootstrapMethodIndex == otherInvoke.getBootstrapMethodIndex()) { + return nat.compareTo(otherInvoke.getNat()); + } else { + return Integer.compare(bootstrapMethodIndex, otherInvoke.getBootstrapMethodIndex()); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstKnownNull.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstKnownNull.java new file mode 100644 index 000000000..a80322c5b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstKnownNull.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; + +/** + * Constant type to represent a known-{@code null} value. + */ +public final class CstKnownNull extends CstLiteralBits { + /** {@code non-null;} unique instance of this class */ + public static final CstKnownNull THE_ONE = new CstKnownNull(); + + /** + * Constructs an instance. This class is not publicly instantiable. Use + * {@link #THE_ONE}. + */ + private CstKnownNull() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + return (other instanceof CstKnownNull); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return 0x4466757a; + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return 0; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "known-null"; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.KNOWN_NULL; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "known-null"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return "null"; + } + + /** {@inheritDoc} */ + @Override + public boolean fitsInInt() { + // See comment in getIntBits(). + return true; + } + + /** + * {@inheritDoc} + * + * As "literal bits," a known-null is always represented as the + * number zero. + */ + @Override + public int getIntBits() { + return 0; + } + + /** + * {@inheritDoc} + * + * As "literal bits," a known-null is always represented as the + * number zero. + */ + @Override + public long getLongBits() { + return 0; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstLiteral32.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstLiteral32.java new file mode 100644 index 000000000..042cbd979 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstLiteral32.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +/** + * Constants which are literal 32-bit values of some sort. + */ +public abstract class CstLiteral32 + extends CstLiteralBits { + /** the value as {@code int} bits */ + private final int bits; + + /** + * Constructs an instance. + * + * @param bits the value as {@code int} bits + */ + /*package*/ CstLiteral32(int bits) { + this.bits = bits; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object other) { + return (other != null) && + (getClass() == other.getClass()) && + bits == ((CstLiteral32) other).bits; + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return bits; + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + int otherBits = ((CstLiteral32) other).bits; + + if (bits < otherBits) { + return -1; + } else if (bits > otherBits) { + return 1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public final boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + @Override + public final boolean fitsInInt() { + return true; + } + + /** {@inheritDoc} */ + @Override + public final int getIntBits() { + return bits; + } + + /** {@inheritDoc} */ + @Override + public final long getLongBits() { + return (long) bits; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstLiteral64.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstLiteral64.java new file mode 100644 index 000000000..94cfa8cf7 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstLiteral64.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +/** + * Constants which are literal 64-bit values of some sort. + */ +public abstract class CstLiteral64 + extends CstLiteralBits { + /** the value as {@code long} bits */ + private final long bits; + + /** + * Constructs an instance. + * + * @param bits the value as {@code long} bits + */ + /*package*/ CstLiteral64(long bits) { + this.bits = bits; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object other) { + return (other != null) && + (getClass() == other.getClass()) && + bits == ((CstLiteral64) other).bits; + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return (int) bits ^ (int) (bits >> 32); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + long otherBits = ((CstLiteral64) other).bits; + + if (bits < otherBits) { + return -1; + } else if (bits > otherBits) { + return 1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public final boolean isCategory2() { + return true; + } + + /** {@inheritDoc} */ + @Override + public final boolean fitsInInt() { + return (int) bits == bits; + } + + /** {@inheritDoc} */ + @Override + public final int getIntBits() { + return (int) bits; + } + + /** {@inheritDoc} */ + @Override + public final long getLongBits() { + return bits; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstLiteralBits.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstLiteralBits.java new file mode 100644 index 000000000..8bf13a2a3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstLiteralBits.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +/** + * Constants which are literal bitwise values of some sort. + */ +public abstract class CstLiteralBits + extends TypedConstant { + /** + * Returns whether or not this instance's value may be accurately + * represented as an {@code int}. The rule is that if there + * is an {@code int} which may be sign-extended to yield this + * instance's value, then this method returns {@code true}. + * Otherwise, it returns {@code false}. + * + * @return {@code true} iff this instance fits in an {@code int} + */ + public abstract boolean fitsInInt(); + + /** + * Gets the value as {@code int} bits. If this instance contains + * more bits than fit in an {@code int}, then this returns only + * the low-order bits. + * + * @return the bits + */ + public abstract int getIntBits(); + + /** + * Gets the value as {@code long} bits. If this instance contains + * fewer bits than fit in a {@code long}, then the result of this + * method is the sign extension of the value. + * + * @return the bits + */ + public abstract long getLongBits(); + + /** + * Returns true if this value can fit in 16 bits with sign-extension. + * + * @return true if the sign-extended lower 16 bits are the same as + * the value. + */ + public boolean fitsIn16Bits() { + if (! fitsInInt()) { + return false; + } + + int bits = getIntBits(); + return (short) bits == bits; + } + + /** + * Returns true if this value can fit in 8 bits with sign-extension. + * + * @return true if the sign-extended lower 8 bits are the same as + * the value. + */ + public boolean fitsIn8Bits() { + if (! fitsInInt()) { + return false; + } + + int bits = getIntBits(); + return (byte) bits == bits; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstLong.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstLong.java new file mode 100644 index 000000000..d15952978 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstLong.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; +import com.android.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Long_info}. + */ +public final class CstLong + extends CstLiteral64 { + /** {@code non-null;} instance representing {@code 0} */ + public static final CstLong VALUE_0 = make(0); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstLong VALUE_1 = make(1); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code long} value + */ + public static CstLong make(long value) { + /* + * Note: Javadoc notwithstanding, this implementation always + * allocates. + */ + return new CstLong(value); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code long} value + */ + private CstLong(long value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + long value = getLongBits(); + return "long{0x" + Hex.u8(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.LONG; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "long"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Long.toString(getLongBits()); + } + + /** + * Gets the {@code long} value. + * + * @return the value + */ + public long getValue() { + return getLongBits(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstMemberRef.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstMemberRef.java new file mode 100644 index 000000000..1775398ed --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstMemberRef.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +/** + * Constants of type {@code CONSTANT_*ref_info}. + */ +public abstract class CstMemberRef extends TypedConstant { + /** {@code non-null;} the type of the defining class */ + private final CstType definingClass; + + /** {@code non-null;} the name-and-type */ + private final CstNat nat; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + /*package*/ CstMemberRef(CstType definingClass, CstNat nat) { + if (definingClass == null) { + throw new NullPointerException("definingClass == null"); + } + + if (nat == null) { + throw new NullPointerException("nat == null"); + } + + this.definingClass = definingClass; + this.nat = nat; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object other) { + if ((other == null) || (getClass() != other.getClass())) { + return false; + } + + CstMemberRef otherRef = (CstMemberRef) other; + return definingClass.equals(otherRef.definingClass) && + nat.equals(otherRef.nat); + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return (definingClass.hashCode() * 31) ^ nat.hashCode(); + } + + /** + * {@inheritDoc} + * + *

Note: This implementation just compares the defining + * class and name, and it is up to subclasses to compare the rest + * after calling {@code super.compareTo0()}.

+ */ + @Override + protected int compareTo0(Constant other) { + CstMemberRef otherMember = (CstMemberRef) other; + int cmp = definingClass.compareTo(otherMember.definingClass); + + if (cmp != 0) { + return cmp; + } + + CstString thisName = nat.getName(); + CstString otherName = otherMember.nat.getName(); + + return thisName.compareTo(otherName); + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + return typeName() + '{' + toHuman() + '}'; + } + + /** {@inheritDoc} */ + @Override + public final boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public final String toHuman() { + return definingClass.toHuman() + '.' + nat.toHuman(); + } + + /** + * Gets the type of the defining class. + * + * @return {@code non-null;} the type of defining class + */ + public final CstType getDefiningClass() { + return definingClass; + } + + /** + * Gets the defining name-and-type. + * + * @return {@code non-null;} the name-and-type + */ + public final CstNat getNat() { + return nat; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstMethodHandle.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstMethodHandle.java new file mode 100644 index 000000000..d5e0f738a --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstMethodHandle.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +/** + * Constants of type {@code MethodHandle}. + */ +public final class CstMethodHandle extends Constant { + + public static final int KIND_GETFIELD = 1; + public static final int KIND_GETSTATIC = 2; + public static final int KIND_PUTFIELD = 3; + public static final int KIND_PUTSTATIC = 4; + public static final int KIND_INVOKEVIRTUAL = 5; + public static final int KIND_INVOKESTATIC = 6; + public static final int KIND_INVOKESPECIAL = 7; + public static final int KIND_NEWINVOKESPECIAL = 8; + public static final int KIND_INVOKEINTERFACE = 9; + + /** The kind of MethodHandle */ + private int kind; + /** {@code non-null;} the referenced constant */ + private final Constant ref; + + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param kind the kind of this handle + * @param ref the actual referenced constant + * @return {@code non-null;} the appropriate instance + */ + public static CstMethodHandle make(int kind, Constant ref) { + return new CstMethodHandle(kind, ref); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param kind the kind of this handle + * @param ref the actual referenced constant + */ + private CstMethodHandle(int kind, Constant ref) { + this.kind = kind; + this.ref = ref; + } + + @Override + public String toString() { + return ref.toString(); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "method handle"; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return toString(); + } + + /** + * Gets the actual constant. + * + * @return the value + */ + public Constant getRef() { + return ref; + } + + /** + * Gets the kind of this method handle. + * + * @return the kind + */ + public int getKind() { + return kind; + } + + @Override + public boolean isCategory2() { + return false; + } + + @Override + protected int compareTo0(Constant other) { + CstMethodHandle otherHandle = (CstMethodHandle) other; + if (getKind() == otherHandle.getKind()) { + return getRef().compareTo(otherHandle.getRef()); + } else { + return Integer.compare(getKind(), otherHandle.getKind()); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstMethodRef.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstMethodRef.java new file mode 100644 index 000000000..075bc7cca --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstMethodRef.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +/** + * Constants of type {@code CONSTANT_Methodref_info}. + */ +public final class CstMethodRef + extends CstBaseMethodRef { + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + public CstMethodRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "method"; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstMethodType.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstMethodType.java new file mode 100644 index 000000000..29a1964c5 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstMethodType.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Prototype; + +/** + * Constants of type {@code MethodType}. + */ +public final class CstMethodType extends Constant { + /** {@code non-null;} the raw prototype for this method */ + private final Prototype prototype; + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param descriptor the method descriptor + * @return {@code non-null;} the appropriate instance + */ + public static CstMethodType make(CstString descriptor) { + return new CstMethodType(descriptor); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param descriptor the method descriptor + */ + private CstMethodType(CstString descriptor) { + prototype = Prototype.fromDescriptor(descriptor.getString()); + } + + @Override + public String toString() { + return prototype.getDescriptor(); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "method type"; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return toString(); + } + + /** + * Gets the {@code Prototype} value. + * + * @return the value + */ + public Prototype getValue() { + return prototype; + } + + @Override + public boolean isCategory2() { + return false; + } + + @Override + protected int compareTo0(Constant other) { + return prototype.getDescriptor().compareTo( + ((CstMethodType) other).getValue().getDescriptor()); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstNat.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstNat.java new file mode 100644 index 000000000..501e9731e --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstNat.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; + +/** + * Constants of type {@code CONSTANT_NameAndType_info}. + */ +public final class CstNat extends Constant { + /** + * {@code non-null;} the instance for name {@code TYPE} and descriptor + * {@code java.lang.Class}, which is useful when dealing with + * wrapped primitives + */ + public static final CstNat PRIMITIVE_TYPE_NAT = + new CstNat(new CstString("TYPE"), + new CstString("Ljava/lang/Class;")); + + /** {@code non-null;} the name */ + private final CstString name; + + /** {@code non-null;} the descriptor (type) */ + private final CstString descriptor; + + /** + * Constructs an instance. + * + * @param name {@code non-null;} the name + * @param descriptor {@code non-null;} the descriptor + */ + public CstNat(CstString name, CstString descriptor) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + this.name = name; + this.descriptor = descriptor; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstNat)) { + return false; + } + + CstNat otherNat = (CstNat) other; + return name.equals(otherNat.name) && + descriptor.equals(otherNat.descriptor); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (name.hashCode() * 31) ^ descriptor.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + CstNat otherNat = (CstNat) other; + int cmp = name.compareTo(otherNat.name); + + if (cmp != 0) { + return cmp; + } + + return descriptor.compareTo(otherNat.descriptor); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "nat{" + toHuman() + '}'; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "nat"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** + * Gets the name. + * + * @return {@code non-null;} the name + */ + public CstString getName() { + return name; + } + + /** + * Gets the descriptor. + * + * @return {@code non-null;} the descriptor + */ + public CstString getDescriptor() { + return descriptor; + } + + /** + * Returns an unadorned but human-readable version of the name-and-type + * value. + * + * @return {@code non-null;} the human form + */ + public String toHuman() { + return name.toHuman() + ':' + descriptor.toHuman(); + } + + /** + * Gets the field type corresponding to this instance's descriptor. + * This method is only valid to call if the descriptor in fact describes + * a field (and not a method). + * + * @return {@code non-null;} the field type + */ + public Type getFieldType() { + return Type.intern(descriptor.getString()); + } + + /** + * Gets whether this instance has the name of a standard instance + * initialization method. This is just a convenient shorthand for + * {@code getName().getString().equals("")}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isInstanceInit() { + return name.getString().equals(""); + } + + /** + * Gets whether this instance has the name of a standard class + * initialization method. This is just a convenient shorthand for + * {@code getName().getString().equals("")}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isClassInit() { + return name.getString().equals(""); + } + + /** + * Gets whether this instance has the name of a signature + * polymorphic method. + * + * @return {@code true} iff the name suggest it could be + * signature polymorphic method. + */ + public final boolean isSignaturePolymorphic() { + final String INVOKE = "invoke"; + final String INVOKE_EXACT = "invokeExact"; + + int nameLength = name.getUtf8Size(); + if (nameLength == INVOKE.length()) { + return name.getString().equals(INVOKE); + } else if (nameLength == INVOKE_EXACT.length()) { + return name.getString().equals(INVOKE_EXACT); + } + return false; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstProtoRef.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstProtoRef.java new file mode 100644 index 000000000..a5613ce63 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstProtoRef.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Prototype; + +/** + * Prototype reference. + */ +public class CstProtoRef extends Constant { + + /** {@code non-null;} the prototype */ + private final Prototype prototype; + + public CstProtoRef(Prototype prototype) { + this.prototype = prototype; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstProtoRef)) { + return false; + } + CstProtoRef otherCstProtoRef = (CstProtoRef) other; + return getPrototype().equals(otherCstProtoRef.getPrototype()); + } + + @Override + public int hashCode() { + return prototype.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "proto"; + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + CstProtoRef otherCstProtoRef = (CstProtoRef) other; + return prototype.compareTo(otherCstProtoRef.getPrototype()); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return prototype.getDescriptor(); + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + return typeName() + "{" + toHuman() + '}'; + } + + public Prototype getPrototype() { + return prototype; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstShort.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstShort.java new file mode 100644 index 000000000..5be1022f2 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstShort.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; +import com.android.dx.util.Hex; + +/** + * Constants of type {@code short}. + */ +public final class CstShort + extends CstLiteral32 { + /** {@code non-null;} the value {@code 0} as an instance of this class */ + public static final CstShort VALUE_0 = make((short) 0); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code short} value + * @return {@code non-null;} the appropriate instance + */ + public static CstShort make(short value) { + return new CstShort(value); + } + + /** + * Makes an instance for the given {@code int} value. This + * may (but does not necessarily) return an already-allocated + * instance. + * + * @param value the value, which must be in range for a {@code short} + * @return {@code non-null;} the appropriate instance + */ + public static CstShort make(int value) { + short cast = (short) value; + + if (cast != value) { + throw new IllegalArgumentException("bogus short value: " + + value); + } + + return make(cast); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code short} value + */ + private CstShort(short value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "short{0x" + Hex.u2(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.SHORT; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "short"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code short} value. + * + * @return the value + */ + public short getValue() { + return (short) getIntBits(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstString.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstString.java new file mode 100644 index 000000000..a778e976d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstString.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; +import com.android.dx.util.ByteArray; +import com.android.dx.util.Hex; + +/** + * Constants of type {@code CONSTANT_Utf8_info} or {@code CONSTANT_String_info}. + */ +public final class CstString extends TypedConstant { + /** + * {@code non-null;} instance representing {@code ""}, that is, the + * empty string + */ + public static final CstString EMPTY_STRING = new CstString(""); + + /** {@code non-null;} the UTF-8 value as a string */ + private final String string; + + /** {@code non-null;} the UTF-8 value as bytes */ + private final ByteArray bytes; + + /** + * Converts a string into its MUTF-8 form. MUTF-8 differs from normal UTF-8 + * in the handling of character '\0' and surrogate pairs. + * + * @param string {@code non-null;} the string to convert + * @return {@code non-null;} the UTF-8 bytes for it + */ + public static byte[] stringToUtf8Bytes(String string) { + int len = string.length(); + byte[] bytes = new byte[len * 3]; // Avoid having to reallocate. + int outAt = 0; + + for (int i = 0; i < len; i++) { + char c = string.charAt(i); + if ((c != 0) && (c < 0x80)) { + bytes[outAt] = (byte) c; + outAt++; + } else if (c < 0x800) { + bytes[outAt] = (byte) (((c >> 6) & 0x1f) | 0xc0); + bytes[outAt + 1] = (byte) ((c & 0x3f) | 0x80); + outAt += 2; + } else { + bytes[outAt] = (byte) (((c >> 12) & 0x0f) | 0xe0); + bytes[outAt + 1] = (byte) (((c >> 6) & 0x3f) | 0x80); + bytes[outAt + 2] = (byte) ((c & 0x3f) | 0x80); + outAt += 3; + } + } + + byte[] result = new byte[outAt]; + System.arraycopy(bytes, 0, result, 0, outAt); + return result; + } + + /** + * Converts an array of UTF-8 bytes into a string. + * + * @param bytes {@code non-null;} the bytes to convert + * @return {@code non-null;} the converted string + */ + public static String utf8BytesToString(ByteArray bytes) { + int length = bytes.size(); + char[] chars = new char[length]; // This is sized to avoid a realloc. + int outAt = 0; + + for (int at = 0; length > 0; /*at*/) { + int v0 = bytes.getUnsignedByte(at); + char out; + switch (v0 >> 4) { + case 0x00: case 0x01: case 0x02: case 0x03: + case 0x04: case 0x05: case 0x06: case 0x07: { + // 0XXXXXXX -- single-byte encoding + length--; + if (v0 == 0) { + // A single zero byte is illegal. + return throwBadUtf8(v0, at); + } + out = (char) v0; + at++; + break; + } + case 0x0c: case 0x0d: { + // 110XXXXX -- two-byte encoding + length -= 2; + if (length < 0) { + return throwBadUtf8(v0, at); + } + int v1 = bytes.getUnsignedByte(at + 1); + if ((v1 & 0xc0) != 0x80) { + return throwBadUtf8(v1, at + 1); + } + int value = ((v0 & 0x1f) << 6) | (v1 & 0x3f); + if ((value != 0) && (value < 0x80)) { + /* + * This should have been represented with + * one-byte encoding. + */ + return throwBadUtf8(v1, at + 1); + } + out = (char) value; + at += 2; + break; + } + case 0x0e: { + // 1110XXXX -- three-byte encoding + length -= 3; + if (length < 0) { + return throwBadUtf8(v0, at); + } + int v1 = bytes.getUnsignedByte(at + 1); + if ((v1 & 0xc0) != 0x80) { + return throwBadUtf8(v1, at + 1); + } + int v2 = bytes.getUnsignedByte(at + 2); + if ((v1 & 0xc0) != 0x80) { + return throwBadUtf8(v2, at + 2); + } + int value = ((v0 & 0x0f) << 12) | ((v1 & 0x3f) << 6) | + (v2 & 0x3f); + if (value < 0x800) { + /* + * This should have been represented with one- or + * two-byte encoding. + */ + return throwBadUtf8(v2, at + 2); + } + out = (char) value; + at += 3; + break; + } + default: { + // 10XXXXXX, 1111XXXX -- illegal + return throwBadUtf8(v0, at); + } + } + chars[outAt] = out; + outAt++; + } + + return new String(chars, 0, outAt); + } + + /** + * Helper for {@link #utf8BytesToString}, which throws the right + * exception for a bogus utf-8 byte. + * + * @param value the byte value + * @param offset the file offset + * @return never + * @throws IllegalArgumentException always thrown + */ + private static String throwBadUtf8(int value, int offset) { + throw new IllegalArgumentException("bad utf-8 byte " + Hex.u1(value) + + " at offset " + Hex.u4(offset)); + } + + /** + * Constructs an instance from a {@code String}. + * + * @param string {@code non-null;} the UTF-8 value as a string + */ + public CstString(String string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + this.string = string.intern(); + this.bytes = new ByteArray(stringToUtf8Bytes(string)); + } + + /** + * Constructs an instance from some UTF-8 bytes. + * + * @param bytes {@code non-null;} array of the UTF-8 bytes + */ + public CstString(ByteArray bytes) { + if (bytes == null) { + throw new NullPointerException("bytes == null"); + } + + this.bytes = bytes; + this.string = utf8BytesToString(bytes).intern(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstString)) { + return false; + } + + return string.equals(((CstString) other).string); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return string.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return string.compareTo(((CstString) other).string); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "string{\"" + toHuman() + "\"}"; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "utf8"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + int len = string.length(); + StringBuilder sb = new StringBuilder(len * 3 / 2); + + for (int i = 0; i < len; i++) { + char c = string.charAt(i); + if ((c >= ' ') && (c < 0x7f)) { + if ((c == '\'') || (c == '\"') || (c == '\\')) { + sb.append('\\'); + } + sb.append(c); + } else if (c <= 0x7f) { + switch (c) { + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: { + /* + * Represent the character as an octal escape. + * If the next character is a valid octal + * digit, disambiguate by using the + * three-digit form. + */ + char nextChar = + (i < (len - 1)) ? string.charAt(i + 1) : 0; + boolean displayZero = + (nextChar >= '0') && (nextChar <= '7'); + sb.append('\\'); + for (int shift = 6; shift >= 0; shift -= 3) { + char outChar = (char) (((c >> shift) & 7) + '0'); + if ((outChar != '0') || displayZero) { + sb.append(outChar); + displayZero = true; + } + } + if (! displayZero) { + // Ironic edge case: The original value was 0. + sb.append('0'); + } + break; + } + } + } else { + sb.append("\\u"); + sb.append(Character.forDigit(c >> 12, 16)); + sb.append(Character.forDigit((c >> 8) & 0x0f, 16)); + sb.append(Character.forDigit((c >> 4) & 0x0f, 16)); + sb.append(Character.forDigit(c & 0x0f, 16)); + } + } + + return sb.toString(); + } + + /** + * Gets the value as a human-oriented string, surrounded by double + * quotes. + * + * @return {@code non-null;} the quoted string + */ + public String toQuoted() { + return '\"' + toHuman() + '\"'; + } + + /** + * Gets the value as a human-oriented string, surrounded by double + * quotes, but ellipsizes the result if it is longer than the given + * maximum length + * + * @param maxLength {@code >= 5;} the maximum length of the string to return + * @return {@code non-null;} the quoted string + */ + public String toQuoted(int maxLength) { + String string = toHuman(); + int length = string.length(); + String ellipses; + + if (length <= (maxLength - 2)) { + ellipses = ""; + } else { + string = string.substring(0, maxLength - 5); + ellipses = "..."; + } + + return '\"' + string + ellipses + '\"'; + } + + /** + * Gets the UTF-8 value as a string. + * The returned string is always already interned. + * + * @return {@code non-null;} the UTF-8 value as a string + */ + public String getString() { + return string; + } + + /** + * Gets the UTF-8 value as UTF-8 encoded bytes. + * + * @return {@code non-null;} an array of the UTF-8 bytes + */ + public ByteArray getBytes() { + return bytes; + } + + /** + * Gets the size of this instance as UTF-8 code points. That is, + * get the number of bytes in the UTF-8 encoding of this instance. + * + * @return {@code >= 0;} the UTF-8 size + */ + public int getUtf8Size() { + return bytes.size(); + } + + /** + * Gets the size of this instance as UTF-16 code points. That is, + * get the number of 16-bit chars in the UTF-16 encoding of this + * instance. This is the same as the {@code length} of the + * Java {@code String} representation of this instance. + * + * @return {@code >= 0;} the UTF-16 size + */ + public int getUtf16Size() { + return string.length(); + } + + public Type getType() { + return Type.STRING; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/CstType.java b/dexlib/src/main/java/com/android/dx/rop/cst/CstType.java new file mode 100644 index 000000000..4b524e513 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/CstType.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.command.dexer.Main; +import com.android.dx.rop.type.Type; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Constants that represent an arbitrary type (reference or primitive). + */ +public final class CstType extends TypedConstant { + + /** + * Intern table for instances. + * + *

The initial capacity is based on a medium-size project. + */ + private static final ConcurrentMap interns = + new ConcurrentHashMap<>(1_000, 0.75f, Main.CONCURRENCY_LEVEL); + + /** {@code non-null;} instance corresponding to the class {@code Object} */ + public static final CstType OBJECT = new CstType(Type.OBJECT); + + /** {@code non-null;} instance corresponding to the class {@code Boolean} */ + public static final CstType BOOLEAN = new CstType(Type.BOOLEAN_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Byte} */ + public static final CstType BYTE = new CstType(Type.BYTE_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Character} */ + public static final CstType CHARACTER = new CstType(Type.CHARACTER_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Double} */ + public static final CstType DOUBLE = new CstType(Type.DOUBLE_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Float} */ + public static final CstType FLOAT = new CstType(Type.FLOAT_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Long} */ + public static final CstType LONG = new CstType(Type.LONG_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Integer} */ + public static final CstType INTEGER = new CstType(Type.INTEGER_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Short} */ + public static final CstType SHORT = new CstType(Type.SHORT_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Void} */ + public static final CstType VOID = new CstType(Type.VOID_CLASS); + + /** {@code non-null;} instance corresponding to the type {@code boolean[]} */ + public static final CstType BOOLEAN_ARRAY = new CstType(Type.BOOLEAN_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code byte[]} */ + public static final CstType BYTE_ARRAY = new CstType(Type.BYTE_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code char[]} */ + public static final CstType CHAR_ARRAY = new CstType(Type.CHAR_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code double[]} */ + public static final CstType DOUBLE_ARRAY = new CstType(Type.DOUBLE_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code float[]} */ + public static final CstType FLOAT_ARRAY = new CstType(Type.FLOAT_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code long[]} */ + public static final CstType LONG_ARRAY = new CstType(Type.LONG_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code int[]} */ + public static final CstType INT_ARRAY = new CstType(Type.INT_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code short[]} */ + public static final CstType SHORT_ARRAY = new CstType(Type.SHORT_ARRAY); + + /** + * {@code non-null;} instance corresponding to the type {@code java.lang.invoke.MethodHandle} + */ + public static final CstType METHOD_HANDLE = new CstType(Type.METHOD_HANDLE); + + static { + initInterns(); + } + + private static void initInterns() { + internInitial(OBJECT); + internInitial(BOOLEAN); + internInitial(BYTE); + internInitial(CHARACTER); + internInitial(DOUBLE); + internInitial(FLOAT); + internInitial(LONG); + internInitial(INTEGER); + internInitial(SHORT); + internInitial(VOID); + internInitial(BOOLEAN_ARRAY); + internInitial(BYTE_ARRAY); + internInitial(CHAR_ARRAY); + internInitial(DOUBLE_ARRAY); + internInitial(FLOAT_ARRAY); + internInitial(LONG_ARRAY); + internInitial(INT_ARRAY); + internInitial(SHORT_ARRAY); + internInitial(METHOD_HANDLE); + } + + private static void internInitial(CstType cst) { + if (interns.putIfAbsent(cst.getClassType(), cst) != null) { + throw new IllegalStateException("Attempted re-init of " + cst); + } + } + + + /** {@code non-null;} the underlying type */ + private final Type type; + + /** + * {@code null-ok;} the type descriptor corresponding to this instance, if + * calculated + */ + private CstString descriptor; + + /** + * Returns an instance of this class that represents the wrapper + * class corresponding to a given primitive type. For example, if + * given {@link Type#INT}, this method returns the class reference + * {@code java.lang.Integer}. + * + * @param primitiveType {@code non-null;} the primitive type + * @return {@code non-null;} the corresponding wrapper class + */ + public static CstType forBoxedPrimitiveType(Type primitiveType) { + switch (primitiveType.getBasicType()) { + case Type.BT_BOOLEAN: return BOOLEAN; + case Type.BT_BYTE: return BYTE; + case Type.BT_CHAR: return CHARACTER; + case Type.BT_DOUBLE: return DOUBLE; + case Type.BT_FLOAT: return FLOAT; + case Type.BT_INT: return INTEGER; + case Type.BT_LONG: return LONG; + case Type.BT_SHORT: return SHORT; + case Type.BT_VOID: return VOID; + } + + throw new IllegalArgumentException("not primitive: " + primitiveType); + } + + /** + * Returns an interned instance of this class for the given type. + * + * @param type {@code non-null;} the underlying type + * @return {@code non-null;} an appropriately-constructed instance + */ + public static CstType intern(Type type) { + CstType cst = new CstType(type); + CstType result = interns.putIfAbsent(type, cst); + return result != null ? result : cst; + } + + /** + * Constructs an instance. + * + * @param type {@code non-null;} the underlying type + */ + public CstType(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (type == Type.KNOWN_NULL) { + throw new UnsupportedOperationException( + "KNOWN_NULL is not representable"); + } + + this.type = type; + this.descriptor = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstType)) { + return false; + } + + return type == ((CstType) other).type; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return type.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + String thisDescriptor = type.getDescriptor(); + String otherDescriptor = ((CstType) other).type.getDescriptor(); + return thisDescriptor.compareTo(otherDescriptor); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "type{" + toHuman() + '}'; + } + + /** {@inheritDoc} */ + @Override + public Type getType() { + return Type.CLASS; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "type"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return type.toHuman(); + } + + /** + * Gets the underlying type (as opposed to the type corresponding + * to this instance as a constant, which is always + * {@code Class}). + * + * @return {@code non-null;} the type corresponding to the name + */ + public Type getClassType() { + return type; + } + + /** + * Gets the type descriptor for this instance. + * + * @return {@code non-null;} the descriptor + */ + public CstString getDescriptor() { + if (descriptor == null) { + descriptor = new CstString(type.getDescriptor()); + } + + return descriptor; + } + + /** + * Returns a human readable package name for this type, like "java.util". + * If this is an array type, this returns the package name of the array's + * component type. If this is a primitive type, this returns "default". + */ + public String getPackageName() { + // descriptor is a string like "[[Ljava/util/String;" + String descriptor = getDescriptor().getString(); + int lastSlash = descriptor.lastIndexOf('/'); + int lastLeftSquare = descriptor.lastIndexOf('['); // -1 unless this is an array + if (lastSlash == -1) { + return "default"; + } else { + // +2 to skip the '[' and the 'L' prefix + return descriptor.substring(lastLeftSquare + 2, lastSlash).replace('/', '.'); + } + } + + public static void clearInternTable() { + interns.clear(); + initInterns(); + } + +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/StdConstantPool.java b/dexlib/src/main/java/com/android/dx/rop/cst/StdConstantPool.java new file mode 100644 index 000000000..f941f7daa --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/StdConstantPool.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dex.util.ExceptionWithContext; +import com.android.dx.util.Hex; +import com.android.dx.util.MutabilityControl; + +/** + * Standard implementation of {@link ConstantPool}, which directly stores + * an array of {@link Constant} objects and can be made immutable. + */ +public final class StdConstantPool + extends MutabilityControl implements ConstantPool { + /** {@code non-null;} array of entries */ + private final Constant[] entries; + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the pool; this corresponds to the + * class file field {@code constant_pool_count}, and is in fact + * always at least one more than the actual size of the constant pool, + * as element {@code 0} is always invalid. + */ + public StdConstantPool(int size) { + super(size > 1); + + if (size < 1) { + throw new IllegalArgumentException("size < 1"); + } + + entries = new Constant[size]; + } + + /** {@inheritDoc} */ + public int size() { + return entries.length; + } + + /** {@inheritDoc} */ + public Constant getOrNull(int n) { + try { + return entries[n]; + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + return throwInvalid(n); + } + } + + /** {@inheritDoc} */ + public Constant get0Ok(int n) { + if (n == 0) { + return null; + } + + return get(n); + } + + /** {@inheritDoc} */ + public Constant get(int n) { + try { + Constant result = entries[n]; + + if (result == null) { + throwInvalid(n); + } + + return result; + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + return throwInvalid(n); + } + } + + /** + * Get all entries in this constant pool. + * + * @return the returned array may contain null entries. + */ + public Constant[] getEntries() { + return entries; + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 1, < size();} which entry + * @param cst {@code null-ok;} the constant to store + */ + public void set(int n, Constant cst) { + throwIfImmutable(); + + boolean cat2 = (cst != null) && cst.isCategory2(); + + if (n < 1) { + throw new IllegalArgumentException("n < 1"); + } + + if (cat2) { + // Storing a category-2 entry nulls out the next index. + if (n == (entries.length - 1)) { + throw new IllegalArgumentException("(n == size - 1) && " + + "cst.isCategory2()"); + } + entries[n + 1] = null; + } + + if ((cst != null) && (entries[n] == null)) { + /* + * Overwriting the second half of a category-2 entry nulls out + * the first half. + */ + Constant prev = entries[n - 1]; + if ((prev != null) && prev.isCategory2()) { + entries[n - 1] = null; + } + } + + entries[n] = cst; + } + + /** + * Throws the right exception for an invalid cpi. + * + * @param idx the bad cpi + * @return never + * @throws ExceptionWithContext always thrown + */ + private static Constant throwInvalid(int idx) { + throw new ExceptionWithContext("invalid constant pool index " + + Hex.u2(idx)); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/TypedConstant.java b/dexlib/src/main/java/com/android/dx/rop/cst/TypedConstant.java new file mode 100644 index 000000000..1c738ee43 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/TypedConstant.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.TypeBearer; + +/** + * Base class for constants which implement {@link TypeBearer}. + */ +public abstract class TypedConstant + extends Constant implements TypeBearer { + /** + * {@inheritDoc} + * + * This implementation always returns {@code this}. + */ + public final TypeBearer getFrameType() { + return this; + } + + /** {@inheritDoc} */ + public final int getBasicType() { + return getType().getBasicType(); + } + + /** {@inheritDoc} */ + public final int getBasicFrameType() { + return getType().getBasicFrameType(); + } + + /** {@inheritDoc} */ + public final boolean isConstant() { + return true; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/Zeroes.java b/dexlib/src/main/java/com/android/dx/rop/cst/Zeroes.java new file mode 100644 index 000000000..7250b5ad9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/Zeroes.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.cst; + +import com.android.dx.rop.type.Type; + +/** + * Utility for turning types into zeroes. + */ +public final class Zeroes { + /** + * This class is uninstantiable. + */ + private Zeroes() { + // This space intentionally left blank. + } + + /** + * Gets the "zero" (or {@code null}) value for the given type. + * + * @param type {@code non-null;} the type in question + * @return {@code non-null;} its "zero" value + */ + public static Constant zeroFor(Type type) { + switch (type.getBasicType()) { + case Type.BT_BOOLEAN: return CstBoolean.VALUE_FALSE; + case Type.BT_BYTE: return CstByte.VALUE_0; + case Type.BT_CHAR: return CstChar.VALUE_0; + case Type.BT_DOUBLE: return CstDouble.VALUE_0; + case Type.BT_FLOAT: return CstFloat.VALUE_0; + case Type.BT_INT: return CstInteger.VALUE_0; + case Type.BT_LONG: return CstLong.VALUE_0; + case Type.BT_SHORT: return CstShort.VALUE_0; + case Type.BT_OBJECT: return CstKnownNull.THE_ONE; + default: { + throw new UnsupportedOperationException("no zero for type: " + + type.toHuman()); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/cst/package.html b/dexlib/src/main/java/com/android/dx/rop/cst/package.html new file mode 100644 index 000000000..c784d163d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/cst/package.html @@ -0,0 +1,9 @@ + +

Interfaces and implementation of things related to the constant pool.

+ +

PACKAGES USED: +

    +
  • com.android.dx.rop.type
  • +
  • com.android.dx.util
  • +
+ diff --git a/dexlib/src/main/java/com/android/dx/rop/package-info.java b/dexlib/src/main/java/com/android/dx/rop/package-info.java new file mode 100644 index 000000000..aaf21eed3 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/package-info.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop; + +/** + *

An Introduction to Rop Form

+ * + * This package contains classes associated with dx's {@code Rop} + * intermediate form.

+ * + * The Rop form is intended to represent the instructions and the control-flow + * graph in a reasonably programmatically useful form while closely mirroring + * the dex instruction set.

+ * + *

Key Classes

+ * + *
    + *
  • {@link RopMethod}, the representation of an individual method + *
  • {@link BasicBlock} and its per-method container, {@link BasicBlockList}, + * the representation of control flow elements. + *
  • {@link Insn} and its subclasses along with its per-basic block + * container {@link InsnList}. {@code Insn} instances represent + * individual instructions in the abstract register machine. + *
  • {@link RegisterSpec} and its container {@link RegisterSpecList}. A + * register spec encodes register number, register width, type information, + * and potentially local variable information as well for instruction sources + * and results. + *
  • {@link Rop} instances represent opcodes in the abstract machine. Many + * {@code Rop} instances are singletons defined in static fields in + * {@link Rops}. The rest are constructed dynamically using static methods + * in {@code Rops} + *
  • {@link RegOps} lists numeric constants for the opcodes + *
  • {@link Constant} and its subclasses represent constant data values + * that opcodes may refer to. + *
  • {@link Type} instances represent the core data types that can be + * handled by the abstract machine. + *
  • The {@link TypeBearer} interface is implemented by classes that + * represent a core data type, but may also have secondary information + * (such as constant value) associated with them. + *
      + * + *

      Control-Flow Graph

      + * + * Each method is separated into a list of basic blocks. For the most part, + * basic blocks are referred to by a positive integer + * {@link BasicBlock#getLabel label}, which is always unique per method. The + * label value is typically derived from a bytecode address from the source + * bytecode. Blocks that don't originate directly from source bytecode have + * labels generated for them in a mostly arbitrary order.

      + * + * Blocks are referred to by their label, for the most part, because + * {@code BasicBlock} instances are immutable and thus any modification to + * the control flow graph or the instruction list results in replacement + * instances (with identical labels) being created.

      + * + * A method has a single {@link RopMethod#getFirstLabel entry block} and 0 + * to N {@link RopMethod#getExitPredecessors exit predecessor blocks} which + * will return. All blocks that are not the entry block will have at least + * one predecessor (or are unreachable and should be removed from the block + * list). All blocks that are not exit predecessors must have at least one + * successor.

      + * + * Since all blocks must branch, all blocks must have, as their final + * instruction, an instruction whose opcode has a {@link Rop#getBranchingness + * branchingness} other than {@link Rop.BRANCH_NONE}. Furthermore, branching + * instructions may only be the final instruction in any basic block. If + * no other terminating opcode is appropriate, use a {@link Rops#GOTO GOTO}.

      + * + * Typically a block will have a {@link BasicBlock#getPrimarySuccessor + * primary successor} which distinguishes a particular control flow path. + * For {Rops#isCallLike}call or call-like} opcodes, this is the path taken + * in the non-exceptional case, where all other successors represent + * various exception paths. For comparison operators such as + * {@link Rops#IF_EQZ_INT}, the primary successor represents the path taken + * if the condition evaluates to false. For {@link SwitchInsn switch + * instructions}, the primary successor is the default case.

      + * + * A basic block's successor list is ordered and may refer to unique labels + * multiple times. For example, if a switch statement contains multiple case + * statements for the same code path, a single basic block will likely + * appear in the successor list multiple times. In general, the + * significance of the successor list's order (like the significance of + * the primary successor) is a property of the final instruction of the basic + * block. A basic block containing a {@link ThrowingInsn}, for example, has + * its successor list in an order identical to the + * {@link ThrowingInsn#getCatches} instruction's catches list, with the + * primary successor (the no-exception case) listed at the end. + * + * It is legal for a basic block to have no primary successor. An obvious + * example of this is a block that terminates in a {@link Rops#THROW throw} + * instruction where a catch block exists inside the current method for that + * exception class. Since the only possible path is the exception path, only + * the exception path (which cannot be a primary successor) is a successor. + * An example of this is shown in {@code dx/tests/092-ssa-cfg-edge-cases}. + * + *

      Rop Instructions

      + * + *

      move-result and move-result-pseudo

      + * + * An instruction that may throw an exception may not specify a result. This + * is necessary because the result register will not be assigned to if an + * exception occurs while processing said instruction and a result assignment + * may not occur. Since result assignments only occur in the non-exceptional + * case, the result assignments for throwing instructions can be said to occur + * at the beginning of the primary successor block rather than at the end of + * the current block. The Rop form represents the result assignments this way. + * Throwing instructions may not directly specify results. Instead, result + * assignments are represented by {@link + * Rops#MOVE_RESULT move-result} or {@link Rops#MOVE_RESULT_PSEUDO + * move-result-pseudo} instructions at the top of the primary successor block. + * + * Only a single {@code move-result} or {@code move-result-pseudo} + * may exist in any block and it must be exactly the first instruction in the + * block. + * + * A {@code move-result} instruction is used for the results of call-like + * instructions. If the value produced by a {@code move-result} is not + * used by the method, it may be eliminated as dead code. + * + * A {@code move-result-pseudo} instruction is used for the results of + * non-call-like throwing instructions. It may never be considered dead code + * since the final dex instruction will always indicate a result register. + * If a required {@code move-result-pseudo} instruction is not found + * during conversion to dex bytecode, an exception will be thrown. + * + *

      move-exception

      + * + * A {@link RegOps.MOVE_EXCEPTION move-exception} instruction may appear at + * the start of a catch block, and represents the obtaining of the thrown + * exception instance. It may only occur as the first instruction in a + * basic block, and any basic block in which it occurs must be reachable only + * as an exception successor. + * + *

      move-param

      + * + * A {@link RegOps.MOVE_PARAM move-param} instruction represents a method + * parameter. Every {@code move-param} instruction is a + * {@link PlainCstInsn}. The index of the method parameter they refer to is + * carried as the {@link CstInteger integer constant} associated with the + * instruction. + * + * Any number of {@code move-param} instructions referring to the same + * parameter index may be included in a method's instruction lists. They + * have no restrictions on placement beyond those of any other + * {@link Rop.BRANCH_NONE} instruction. Note that the SSA optimizer arranges the + * parameter assignments to align with the dex bytecode calling conventions. + * With parameter assignments so arranged, the + * {@link com.android.dx.dex.code.RopTranslator} sees Rop {@code move-param} + * instructions as unnecessary in dex form and eliminates them. + * + *

      mark-local

      + * + * A {@link RegOps.MARK_LOCAL mark-local} instruction indicates that a local + * variable becomes live in a specified register specified register for the + * purposes of debug information. A {@code mark-local} instruction has + * a single source (the register which will now be considered a local variable) + * and no results. The instruction has no side effect.

      + * + * In a typical case, a local variable's lifetime begins with an + * assignment. The local variable whose information is present in a result's + * {@link RegisterSpec#getLocalItem LocalItem} is considered to begin (or move + * between registers) when the instruction is executed.

      + * + * However, sometimes a local variable can begin its life or move without + * an assignment occurring. A common example of this is occurs in the Rop + * representation of the following code:

      + * + *

      + * try {
      + *     Object foo = null;
      + *     foo = new Object();
      + * } catch (Throwable ex) { }
      + * 
      + * + * An object's initialization occurs in two steps. First, a + * {@code new-instance} instruction is executed, whose result is stored in a + * register. However, that register can not yet be considered to contain + * "foo". That's because the instance's constructor method must be called + * via an {@code invoke} instruction. The constructor method, however, may + * throw an exception. And if an exception occurs, then "foo" should remain + * null. So "foo" becomes the value of the result of the {@code new-instance} + * instruction after the (void) constructor method is invoked and + * returns successfully. In such a case, a {@code mark-local} will + * typically occur at the beginning of the primary successor block following + * the invocation to the constructor. + */ diff --git a/dexlib/src/main/java/com/android/dx/rop/type/Prototype.java b/dexlib/src/main/java/com/android/dx/rop/type/Prototype.java new file mode 100644 index 000000000..c54c86915 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/type/Prototype.java @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.type; + +import com.android.dx.command.dexer.Main; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Representation of a method descriptor. Instances of this class are + * generally interned and may be usefully compared with each other + * using {@code ==}. + */ +public final class Prototype implements Comparable { + /** + * Intern table for instances. + * + *

      The initial capacity is based on a medium-size project. + */ + private static final ConcurrentMap internTable = + new ConcurrentHashMap<>(10_000, 0.75f, Main.CONCURRENCY_LEVEL); + + /** {@code non-null;} method descriptor */ + private final String descriptor; + + /** {@code non-null;} return type */ + private final Type returnType; + + /** {@code non-null;} list of parameter types */ + private final StdTypeList parameterTypes; + + /** {@code null-ok;} list of parameter frame types, if calculated */ + private StdTypeList parameterFrameTypes; + + /** + * Returns the unique instance corresponding to the + * given method descriptor. See vmspec-2 sec4.3.3 for details on the + * field descriptor syntax. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Prototype intern(String descriptor) { + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + Prototype result = internTable.get(descriptor); + if (result != null) { + return result; + } + + result = fromDescriptor(descriptor); + return putIntern(result); + } + + /** + * Returns a prototype for a method descriptor. + * + * The {@code Prototype} returned will be the interned value if present, + * or a new instance otherwise. If a new instance is created, it is not + * placed in the intern table. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Prototype fromDescriptor(String descriptor) { + Prototype result = internTable.get(descriptor); + if (result != null) { + return result; + } + + Type[] params = makeParameterArray(descriptor); + int paramCount = 0; + int at = 1; + + for (;;) { + int startAt = at; + char c = descriptor.charAt(at); + if (c == ')') { + at++; + break; + } + + // Skip array markers. + while (c == '[') { + at++; + c = descriptor.charAt(at); + } + + if (c == 'L') { + // It looks like the start of a class name; find the end. + int endAt = descriptor.indexOf(';', at); + if (endAt == -1) { + throw new IllegalArgumentException("bad descriptor"); + } + at = endAt + 1; + } else { + at++; + } + + params[paramCount] = + Type.intern(descriptor.substring(startAt, at)); + paramCount++; + } + + Type returnType = Type.internReturnType(descriptor.substring(at)); + StdTypeList parameterTypes = new StdTypeList(paramCount); + + for (int i = 0; i < paramCount; i++) { + parameterTypes.set(i, params[i]); + } + + return new Prototype(descriptor, returnType, parameterTypes); + } + + public static void clearInternTable() { + internTable.clear(); + } + + /** + * Helper for {@link #intern} which returns an empty array to + * populate with parsed parameter types, and which also ensures + * that there is a '(' at the start of the descriptor and a + * single ')' somewhere before the end. + * + * @param descriptor {@code non-null;} the descriptor string + * @return {@code non-null;} array large enough to hold all parsed parameter + * types, but which is likely actually larger than needed + */ + private static Type[] makeParameterArray(String descriptor) { + int length = descriptor.length(); + + if (descriptor.charAt(0) != '(') { + throw new IllegalArgumentException("bad descriptor"); + } + + /* + * This is a cheesy way to establish an upper bound on the + * number of parameters: Just count capital letters. + */ + int closeAt = 0; + int maxParams = 0; + for (int i = 1; i < length; i++) { + char c = descriptor.charAt(i); + if (c == ')') { + closeAt = i; + break; + } + if ((c >= 'A') && (c <= 'Z')) { + maxParams++; + } + } + + if ((closeAt == 0) || (closeAt == (length - 1))) { + throw new IllegalArgumentException("bad descriptor"); + } + + if (descriptor.indexOf(')', closeAt + 1) != -1) { + throw new IllegalArgumentException("bad descriptor"); + } + + return new Type[maxParams]; + } + + /** + * Interns an instance, adding to the descriptor as necessary based + * on the given definer, name, and flags. For example, an init + * method has an uninitialized object of type {@code definer} + * as its first argument. + * + * @param descriptor {@code non-null;} the descriptor string + * @param definer {@code non-null;} class the method is defined on + * @param isStatic whether this is a static method + * @param isInit whether this is an init method + * @return {@code non-null;} the interned instance + */ + public static Prototype intern(String descriptor, Type definer, + boolean isStatic, boolean isInit) { + Prototype base = intern(descriptor); + + if (isStatic) { + return base; + } + + if (isInit) { + definer = definer.asUninitialized(Integer.MAX_VALUE); + } + + return base.withFirstParameter(definer); + } + + /** + * Interns an instance which consists of the given number of + * {@code int}s along with the given return type + * + * @param returnType {@code non-null;} the return type + * @param count {@code > 0;} the number of elements in the prototype + * @return {@code non-null;} the interned instance + */ + public static Prototype internInts(Type returnType, int count) { + // Make the descriptor... + + StringBuffer sb = new StringBuffer(100); + + sb.append('('); + + for (int i = 0; i < count; i++) { + sb.append('I'); + } + + sb.append(')'); + sb.append(returnType.getDescriptor()); + + // ...and intern it. + return intern(sb.toString()); + } + + /** + * Constructs an instance. This is a private constructor; use one + * of the public static methods to get instances. + * + * @param descriptor {@code non-null;} the descriptor string + */ + private Prototype(String descriptor, Type returnType, + StdTypeList parameterTypes) { + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + if (returnType == null) { + throw new NullPointerException("returnType == null"); + } + + if (parameterTypes == null) { + throw new NullPointerException("parameterTypes == null"); + } + + this.descriptor = descriptor; + this.returnType = returnType; + this.parameterTypes = parameterTypes; + this.parameterFrameTypes = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + /* + * Since externally-visible instances are interned, this + * check helps weed out some easy cases. + */ + return true; + } + + if (!(other instanceof Prototype)) { + return false; + } + + return descriptor.equals(((Prototype) other).descriptor); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return descriptor.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public int compareTo(Prototype other) { + if (this == other) { + return 0; + } + + /* + * The return type is the major order, and then args in order, + * and then the shorter list comes first (similar to string + * sorting). + */ + + int result = returnType.compareTo(other.returnType); + + if (result != 0) { + return result; + } + + int thisSize = parameterTypes.size(); + int otherSize = other.parameterTypes.size(); + int size = Math.min(thisSize, otherSize); + + for (int i = 0; i < size; i++) { + Type thisType = parameterTypes.get(i); + Type otherType = other.parameterTypes.get(i); + + result = thisType.compareTo(otherType); + + if (result != 0) { + return result; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return descriptor; + } + + /** + * Gets the descriptor string. + * + * @return {@code non-null;} the descriptor + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Gets the return type. + * + * @return {@code non-null;} the return type + */ + public Type getReturnType() { + return returnType; + } + + /** + * Gets the list of parameter types. + * + * @return {@code non-null;} the list of parameter types + */ + public StdTypeList getParameterTypes() { + return parameterTypes; + } + + /** + * Gets the list of frame types corresponding to the list of parameter + * types. The difference between the two lists (if any) is that all + * "intlike" types (see {@link Type#isIntlike}) are replaced by + * {@link Type#INT}. + * + * @return {@code non-null;} the list of parameter frame types + */ + public StdTypeList getParameterFrameTypes() { + if (parameterFrameTypes == null) { + int sz = parameterTypes.size(); + StdTypeList list = new StdTypeList(sz); + boolean any = false; + for (int i = 0; i < sz; i++) { + Type one = parameterTypes.get(i); + if (one.isIntlike()) { + any = true; + one = Type.INT; + } + list.set(i, one); + } + parameterFrameTypes = any ? list : parameterTypes; + } + + return parameterFrameTypes; + } + + /** + * Returns a new interned instance, which is the same as this instance, + * except that it has an additional parameter prepended to the original's + * argument list. + * + * @param param {@code non-null;} the new first parameter + * @return {@code non-null;} an appropriately-constructed instance + */ + public Prototype withFirstParameter(Type param) { + String newDesc = "(" + param.getDescriptor() + descriptor.substring(1); + StdTypeList newParams = parameterTypes.withFirst(param); + + newParams.setImmutable(); + + Prototype result = + new Prototype(newDesc, returnType, newParams); + + return putIntern(result); + } + + /** + * Puts the given instance in the intern table if it's not already + * there. If a conflicting value is already in the table, then leave it. + * Return the interned value. + * + * @param desc {@code non-null;} instance to make interned + * @return {@code non-null;} the actual interned object + */ + private static Prototype putIntern(Prototype desc) { + Prototype result = internTable.putIfAbsent(desc.getDescriptor(), desc); + return result != null ? result : desc; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/type/StdTypeList.java b/dexlib/src/main/java/com/android/dx/rop/type/StdTypeList.java new file mode 100644 index 000000000..fe6647c03 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/type/StdTypeList.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.type; + +import com.android.dx.util.FixedSizeList; + +/** + * Standard implementation of {@link TypeList}. + */ +public final class StdTypeList + extends FixedSizeList implements TypeList { + /** {@code non-null;} no-element instance */ + public static final StdTypeList EMPTY = new StdTypeList(0); + + /** {@code non-null;} the list {@code [int]} */ + public static final StdTypeList INT = StdTypeList.make(Type.INT); + + /** {@code non-null;} the list {@code [long]} */ + public static final StdTypeList LONG = StdTypeList.make(Type.LONG); + + /** {@code non-null;} the list {@code [float]} */ + public static final StdTypeList FLOAT = StdTypeList.make(Type.FLOAT); + + /** {@code non-null;} the list {@code [double]} */ + public static final StdTypeList DOUBLE = StdTypeList.make(Type.DOUBLE); + + /** {@code non-null;} the list {@code [Object]} */ + public static final StdTypeList OBJECT = StdTypeList.make(Type.OBJECT); + + /** {@code non-null;} the list {@code [ReturnAddress]} */ + public static final StdTypeList RETURN_ADDRESS + = StdTypeList.make(Type.RETURN_ADDRESS); + + /** {@code non-null;} the list {@code [Throwable]} */ + public static final StdTypeList THROWABLE = + StdTypeList.make(Type.THROWABLE); + + /** {@code non-null;} the list {@code [int, int]} */ + public static final StdTypeList INT_INT = + StdTypeList.make(Type.INT, Type.INT); + + /** {@code non-null;} the list {@code [long, long]} */ + public static final StdTypeList LONG_LONG = + StdTypeList.make(Type.LONG, Type.LONG); + + /** {@code non-null;} the list {@code [float, float]} */ + public static final StdTypeList FLOAT_FLOAT = + StdTypeList.make(Type.FLOAT, Type.FLOAT); + + /** {@code non-null;} the list {@code [double, double]} */ + public static final StdTypeList DOUBLE_DOUBLE = + StdTypeList.make(Type.DOUBLE, Type.DOUBLE); + + /** {@code non-null;} the list {@code [Object, Object]} */ + public static final StdTypeList OBJECT_OBJECT = + StdTypeList.make(Type.OBJECT, Type.OBJECT); + + /** {@code non-null;} the list {@code [int, Object]} */ + public static final StdTypeList INT_OBJECT = + StdTypeList.make(Type.INT, Type.OBJECT); + + /** {@code non-null;} the list {@code [long, Object]} */ + public static final StdTypeList LONG_OBJECT = + StdTypeList.make(Type.LONG, Type.OBJECT); + + /** {@code non-null;} the list {@code [float, Object]} */ + public static final StdTypeList FLOAT_OBJECT = + StdTypeList.make(Type.FLOAT, Type.OBJECT); + + /** {@code non-null;} the list {@code [double, Object]} */ + public static final StdTypeList DOUBLE_OBJECT = + StdTypeList.make(Type.DOUBLE, Type.OBJECT); + + /** {@code non-null;} the list {@code [long, int]} */ + public static final StdTypeList LONG_INT = + StdTypeList.make(Type.LONG, Type.INT); + + /** {@code non-null;} the list {@code [int[], int]} */ + public static final StdTypeList INTARR_INT = + StdTypeList.make(Type.INT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [long[], int]} */ + public static final StdTypeList LONGARR_INT = + StdTypeList.make(Type.LONG_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [float[], int]} */ + public static final StdTypeList FLOATARR_INT = + StdTypeList.make(Type.FLOAT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [double[], int]} */ + public static final StdTypeList DOUBLEARR_INT = + StdTypeList.make(Type.DOUBLE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [Object[], int]} */ + public static final StdTypeList OBJECTARR_INT = + StdTypeList.make(Type.OBJECT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [boolean[], int]} */ + public static final StdTypeList BOOLEANARR_INT = + StdTypeList.make(Type.BOOLEAN_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [byte[], int]} */ + public static final StdTypeList BYTEARR_INT = + StdTypeList.make(Type.BYTE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [char[], int]} */ + public static final StdTypeList CHARARR_INT = + StdTypeList.make(Type.CHAR_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [short[], int]} */ + public static final StdTypeList SHORTARR_INT = + StdTypeList.make(Type.SHORT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, int[], int]} */ + public static final StdTypeList INT_INTARR_INT = + StdTypeList.make(Type.INT, Type.INT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [long, long[], int]} */ + public static final StdTypeList LONG_LONGARR_INT = + StdTypeList.make(Type.LONG, Type.LONG_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [float, float[], int]} */ + public static final StdTypeList FLOAT_FLOATARR_INT = + StdTypeList.make(Type.FLOAT, Type.FLOAT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [double, double[], int]} */ + public static final StdTypeList DOUBLE_DOUBLEARR_INT = + StdTypeList.make(Type.DOUBLE, Type.DOUBLE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [Object, Object[], int]} */ + public static final StdTypeList OBJECT_OBJECTARR_INT = + StdTypeList.make(Type.OBJECT, Type.OBJECT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, boolean[], int]} */ + public static final StdTypeList INT_BOOLEANARR_INT = + StdTypeList.make(Type.INT, Type.BOOLEAN_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, byte[], int]} */ + public static final StdTypeList INT_BYTEARR_INT = + StdTypeList.make(Type.INT, Type.BYTE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, char[], int]} */ + public static final StdTypeList INT_CHARARR_INT = + StdTypeList.make(Type.INT, Type.CHAR_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, short[], int]} */ + public static final StdTypeList INT_SHORTARR_INT = + StdTypeList.make(Type.INT, Type.SHORT_ARRAY, Type.INT); + + /** + * Makes a single-element instance. + * + * @param type {@code non-null;} the element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type) { + StdTypeList result = new StdTypeList(1); + result.set(0, type); + return result; + } + + /** + * Makes a two-element instance. + * + * @param type0 {@code non-null;} the first element + * @param type1 {@code non-null;} the second element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type0, Type type1) { + StdTypeList result = new StdTypeList(2); + result.set(0, type0); + result.set(1, type1); + return result; + } + + /** + * Makes a three-element instance. + * + * @param type0 {@code non-null;} the first element + * @param type1 {@code non-null;} the second element + * @param type2 {@code non-null;} the third element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type0, Type type1, Type type2) { + StdTypeList result = new StdTypeList(3); + result.set(0, type0); + result.set(1, type1); + result.set(2, type2); + return result; + } + + /** + * Makes a four-element instance. + * + * @param type0 {@code non-null;} the first element + * @param type1 {@code non-null;} the second element + * @param type2 {@code non-null;} the third element + * @param type3 {@code non-null;} the fourth element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type0, Type type1, Type type2, + Type type3) { + StdTypeList result = new StdTypeList(4); + result.set(0, type0); + result.set(1, type1); + result.set(2, type2); + result.set(3, type3); + return result; + } + + /** + * Returns the given list as a comma-separated list of human forms. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list {@code non-null;} the list to convert + * @return {@code non-null;} the human form + */ + public static String toHuman(TypeList list) { + int size = list.size(); + + if (size == 0) { + return ""; + } + + StringBuffer sb = new StringBuffer(100); + + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(list.getType(i).toHuman()); + } + + return sb.toString(); + } + + /** + * Returns a hashcode of the contents of the given list. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list {@code non-null;} the list to inspect + * @return {@code non-null;} the hash code + */ + public static int hashContents(TypeList list) { + int size = list.size(); + int hash = 0; + + for (int i = 0; i < size; i++) { + hash = (hash * 31) + list.getType(i).hashCode(); + } + + return hash; + } + + /** + * Compares the contents of the given two instances for equality. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list1 {@code non-null;} one list to compare + * @param list2 {@code non-null;} another list to compare + * @return whether the two lists contain corresponding equal elements + */ + public static boolean equalContents(TypeList list1, TypeList list2) { + int size = list1.size(); + + if (list2.size() != size) { + return false; + } + + for (int i = 0; i < size; i++) { + if (! list1.getType(i).equals(list2.getType(i))) { + return false; + } + } + + return true; + } + + /** + * Compares the contents of the given two instances for ordering. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list1 {@code non-null;} one list to compare + * @param list2 {@code non-null;} another list to compare + * @return the order of the two lists + */ + public static int compareContents(TypeList list1, TypeList list2) { + int size1 = list1.size(); + int size2 = list2.size(); + int size = Math.min(size1, size2); + + for (int i = 0; i < size; i++) { + int comparison = list1.getType(i).compareTo(list2.getType(i)); + if (comparison != 0) { + return comparison; + } + } + + if (size1 == size2) { + return 0; + } else if (size1 < size2) { + return -1; + } else { + return 1; + } + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public StdTypeList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Type getType(int n) { + return get(n); + } + + /** {@inheritDoc} */ + public int getWordCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + result += get(i).getCategory(); + } + + return result; + } + + /** {@inheritDoc} */ + public TypeList withAddedType(Type type) { + int sz = size(); + StdTypeList result = new StdTypeList(sz + 1); + + for (int i = 0; i < sz; i++) { + result.set0(i, get0(i)); + } + + result.set(sz, type); + result.setImmutable(); + return result; + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public Type get(int n) { + return (Type) get0(n); + } + + /** + * Sets the type at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param type {@code non-null;} the type to store + */ + public void set(int n, Type type) { + set0(n, type); + } + + /** + * Returns a new instance, which is the same as this instance, + * except that it has an additional type prepended to the + * original. + * + * @param type {@code non-null;} the new first element + * @return {@code non-null;} an appropriately-constructed instance + */ + public StdTypeList withFirst(Type type) { + int sz = size(); + StdTypeList result = new StdTypeList(sz + 1); + + result.set0(0, type); + for (int i = 0; i < sz; i++) { + result.set0(i + 1, getOrNull0(i)); + } + + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/type/Type.java b/dexlib/src/main/java/com/android/dx/rop/type/Type.java new file mode 100644 index 000000000..d7879eb96 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/type/Type.java @@ -0,0 +1,907 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.type; + +import com.android.dx.command.dexer.Main; +import com.android.dx.util.Hex; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Representation of a value type, such as may appear in a field, in a + * local, on a stack, or in a method descriptor. Instances of this + * class are generally interned and may be usefully compared with each + * other using {@code ==}. + */ +public final class Type implements TypeBearer, Comparable { + /** + * Intern table for instances. + * + *

      The initial capacity is based on a medium-size project. + */ + private static final ConcurrentMap internTable = + new ConcurrentHashMap<>(10_000, 0.75f, Main.CONCURRENCY_LEVEL); + + + /** basic type constant for {@code void} */ + public static final int BT_VOID = 0; + + /** basic type constant for {@code boolean} */ + public static final int BT_BOOLEAN = 1; + + /** basic type constant for {@code byte} */ + public static final int BT_BYTE = 2; + + /** basic type constant for {@code char} */ + public static final int BT_CHAR = 3; + + /** basic type constant for {@code double} */ + public static final int BT_DOUBLE = 4; + + /** basic type constant for {@code float} */ + public static final int BT_FLOAT = 5; + + /** basic type constant for {@code int} */ + public static final int BT_INT = 6; + + /** basic type constant for {@code long} */ + public static final int BT_LONG = 7; + + /** basic type constant for {@code short} */ + public static final int BT_SHORT = 8; + + /** basic type constant for {@code Object} */ + public static final int BT_OBJECT = 9; + + /** basic type constant for a return address */ + public static final int BT_ADDR = 10; + + /** count of basic type constants */ + public static final int BT_COUNT = 11; + + /** {@code non-null;} instance representing {@code boolean} */ + public static final Type BOOLEAN = new Type("Z", BT_BOOLEAN); + + /** {@code non-null;} instance representing {@code byte} */ + public static final Type BYTE = new Type("B", BT_BYTE); + + /** {@code non-null;} instance representing {@code char} */ + public static final Type CHAR = new Type("C", BT_CHAR); + + /** {@code non-null;} instance representing {@code double} */ + public static final Type DOUBLE = new Type("D", BT_DOUBLE); + + /** {@code non-null;} instance representing {@code float} */ + public static final Type FLOAT = new Type("F", BT_FLOAT); + + /** {@code non-null;} instance representing {@code int} */ + public static final Type INT = new Type("I", BT_INT); + + /** {@code non-null;} instance representing {@code long} */ + public static final Type LONG = new Type("J", BT_LONG); + + /** {@code non-null;} instance representing {@code short} */ + public static final Type SHORT = new Type("S", BT_SHORT); + + /** {@code non-null;} instance representing {@code void} */ + public static final Type VOID = new Type("V", BT_VOID); + + /** {@code non-null;} instance representing a known-{@code null} */ + public static final Type KNOWN_NULL = new Type("", BT_OBJECT); + + /** {@code non-null;} instance representing a subroutine return address */ + public static final Type RETURN_ADDRESS = new Type("", BT_ADDR); + + + /** + * {@code non-null;} instance representing + * {@code java.lang.annotation.Annotation} + */ + public static final Type ANNOTATION = + new Type("Ljava/lang/annotation/Annotation;", BT_OBJECT); + + /** {@code non-null;} instance representing {@code java.lang.Class} */ + public static final Type CLASS = new Type("Ljava/lang/Class;", BT_OBJECT); + + /** {@code non-null;} instance representing {@code java.lang.Cloneable} */ + public static final Type CLONEABLE = new Type("Ljava/lang/Cloneable;", BT_OBJECT); + + /** {@code non-null;} instance representing {@code java.lang.invoke.MethodHandle} */ + public static final Type METHOD_HANDLE = new Type("Ljava/lang/invoke/MethodHandle;", BT_OBJECT); + + /** {@code non-null;} instance representing {@code java.lang.Object} */ + public static final Type OBJECT = new Type("Ljava/lang/Object;", BT_OBJECT); + + /** {@code non-null;} instance representing {@code java.io.Serializable} */ + public static final Type SERIALIZABLE = new Type("Ljava/io/Serializable;", BT_OBJECT); + + /** {@code non-null;} instance representing {@code java.lang.String} */ + public static final Type STRING = new Type("Ljava/lang/String;", BT_OBJECT); + + /** {@code non-null;} instance representing {@code java.lang.Throwable} */ + public static final Type THROWABLE = new Type("Ljava/lang/Throwable;", BT_OBJECT); + + /** + * {@code non-null;} instance representing {@code java.lang.Boolean}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type BOOLEAN_CLASS = new Type("Ljava/lang/Boolean;", BT_OBJECT); + + /** + * {@code non-null;} instance representing {@code java.lang.Byte}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type BYTE_CLASS = new Type("Ljava/lang/Byte;", BT_OBJECT); + + /** + * {@code non-null;} instance representing {@code java.lang.Character}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type CHARACTER_CLASS = new Type("Ljava/lang/Character;", BT_OBJECT); + + /** + * {@code non-null;} instance representing {@code java.lang.Double}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type DOUBLE_CLASS = new Type("Ljava/lang/Double;", BT_OBJECT); + + /** + * {@code non-null;} instance representing {@code java.lang.Float}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type FLOAT_CLASS = new Type("Ljava/lang/Float;", BT_OBJECT); + + /** + * {@code non-null;} instance representing {@code java.lang.Integer}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type INTEGER_CLASS = new Type("Ljava/lang/Integer;", BT_OBJECT); + + /** + * {@code non-null;} instance representing {@code java.lang.Long}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type LONG_CLASS = new Type("Ljava/lang/Long;", BT_OBJECT); + + /** + * {@code non-null;} instance representing {@code java.lang.Short}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type SHORT_CLASS = new Type("Ljava/lang/Short;", BT_OBJECT); + + /** + * {@code non-null;} instance representing {@code java.lang.Void}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type VOID_CLASS = new Type("Ljava/lang/Void;", BT_OBJECT); + + /** {@code non-null;} instance representing {@code boolean[]} */ + public static final Type BOOLEAN_ARRAY = new Type("[" + BOOLEAN.descriptor, BT_OBJECT); + + /** {@code non-null;} instance representing {@code byte[]} */ + public static final Type BYTE_ARRAY = new Type("[" + BYTE.descriptor, BT_OBJECT); + + /** {@code non-null;} instance representing {@code char[]} */ + public static final Type CHAR_ARRAY = new Type("[" + CHAR.descriptor, BT_OBJECT); + + /** {@code non-null;} instance representing {@code double[]} */ + public static final Type DOUBLE_ARRAY = new Type("[" + DOUBLE.descriptor, BT_OBJECT); + + /** {@code non-null;} instance representing {@code float[]} */ + public static final Type FLOAT_ARRAY = new Type("[" + FLOAT.descriptor, BT_OBJECT);; + + /** {@code non-null;} instance representing {@code int[]} */ + public static final Type INT_ARRAY = new Type("[" + INT.descriptor, BT_OBJECT); + + /** {@code non-null;} instance representing {@code long[]} */ + public static final Type LONG_ARRAY = new Type("[" + LONG.descriptor, BT_OBJECT); + + /** {@code non-null;} instance representing {@code Object[]} */ + public static final Type OBJECT_ARRAY = new Type("[" + OBJECT.descriptor, BT_OBJECT); + + /** {@code non-null;} instance representing {@code short[]} */ + public static final Type SHORT_ARRAY = new Type("[" + SHORT.descriptor, BT_OBJECT); + + static { + initInterns(); + } + + /** + * Put the constant fields, including primitive types in to the intern table. + * + *

      Must be called after the types are initialized above. + */ + private static void initInterns() { + putIntern(BOOLEAN); + putIntern(BYTE); + putIntern(CHAR); + putIntern(DOUBLE); + putIntern(FLOAT); + putIntern(INT); + putIntern(LONG); + putIntern(SHORT); + /* + * Note: VOID isn't put in the intern table, since it's special and + * shouldn't be found by a normal call to intern(). + */ + + putIntern(ANNOTATION); + putIntern(CLASS); + putIntern(CLONEABLE); + putIntern(METHOD_HANDLE); + putIntern(OBJECT); + putIntern(SERIALIZABLE); + putIntern(STRING); + putIntern(THROWABLE); + putIntern(BOOLEAN_CLASS); + putIntern(BYTE_CLASS); + putIntern(CHARACTER_CLASS); + putIntern(DOUBLE_CLASS); + putIntern(FLOAT_CLASS); + putIntern(INTEGER_CLASS); + putIntern(LONG_CLASS); + putIntern(SHORT_CLASS); + putIntern(VOID_CLASS); + + // Array types + putIntern(BOOLEAN_ARRAY); + putIntern(BYTE_ARRAY); + putIntern(CHAR_ARRAY); + putIntern(DOUBLE_ARRAY); + putIntern(FLOAT_ARRAY); + putIntern(INT_ARRAY); + putIntern(LONG_ARRAY); + putIntern(OBJECT_ARRAY); + putIntern(SHORT_ARRAY); + } + + + /** {@code non-null;} field descriptor for the type */ + private final String descriptor; + + /** + * basic type corresponding to this type; one of the + * {@code BT_*} constants + */ + private final int basicType; + + /** + * {@code >= -1;} for an uninitialized type, bytecode index that this + * instance was allocated at; {@code Integer.MAX_VALUE} if it + * was an incoming uninitialized instance; {@code -1} if this + * is an inititialized instance + */ + private final int newAt; + + /** + * {@code null-ok;} the internal-form class name corresponding to + * this type, if calculated; only valid if {@code this} is a + * reference type and additionally not a return address + */ + private String className; + + /** + * {@code null-ok;} the type corresponding to an array of this type, if + * calculated + */ + private Type arrayType; + + /** + * {@code null-ok;} the type corresponding to elements of this type, if + * calculated; only valid if {@code this} is an array type + */ + private Type componentType; + + /** + * {@code null-ok;} the type corresponding to the initialized version of + * this type, if this instance is in fact an uninitialized type + */ + private Type initializedType; + + /** + * Returns the unique instance corresponding to the type with the + * given descriptor. See vmspec-2 sec4.3.2 for details on the + * field descriptor syntax. This method does not allow + * {@code "V"} (that is, type {@code void}) as a valid + * descriptor. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Type intern(String descriptor) { + Type result = internTable.get(descriptor); + + if (result != null) { + return result; + } + + char firstChar; + try { + firstChar = descriptor.charAt(0); + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("descriptor is empty"); + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("descriptor == null"); + } + + if (firstChar == '[') { + /* + * Recursively strip away array markers to get at the underlying + * type, and build back on to form the result. + */ + result = intern(descriptor.substring(1)); + return result.getArrayType(); + } + + /* + * If the first character isn't '[' and it wasn't found in the + * intern cache, then it had better be the descriptor for a class. + */ + + int length = descriptor.length(); + if ((firstChar != 'L') || + (descriptor.charAt(length - 1) != ';')) { + throw new IllegalArgumentException("bad descriptor: " + descriptor); + } + + /* + * Validate the characters of the class name itself. Note that + * vmspec-2 does not have a coherent definition for valid + * internal-form class names, and the definition here is fairly + * liberal: A name is considered valid as long as it doesn't + * contain any of '[' ';' '.' '(' ')', and it has no more than one + * '/' in a row, and no '/' at either end. + */ + + int limit = (length - 1); // Skip the final ';'. + for (int i = 1; i < limit; i++) { + char c = descriptor.charAt(i); + switch (c) { + case '[': + case ';': + case '.': + case '(': + case ')': { + throw new IllegalArgumentException("bad descriptor: " + descriptor); + } + case '/': { + if ((i == 1) || + (i == (length - 1)) || + (descriptor.charAt(i - 1) == '/')) { + throw new IllegalArgumentException("bad descriptor: " + descriptor); + } + break; + } + } + } + + result = new Type(descriptor, BT_OBJECT); + return putIntern(result); + } + + /** + * Returns the unique instance corresponding to the type with the + * given descriptor, allowing {@code "V"} to return the type + * for {@code void}. Other than that one caveat, this method + * is identical to {@link #intern}. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Type internReturnType(String descriptor) { + try { + if (descriptor.equals("V")) { + // This is the one special case where void may be returned. + return VOID; + } + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("descriptor == null"); + } + + return intern(descriptor); + } + + /** + * Returns the unique instance corresponding to the type of the + * class with the given name. Calling this method is equivalent to + * calling {@code intern(name)} if {@code name} begins + * with {@code "["} and calling {@code intern("L" + name + ";")} + * in all other cases. + * + * @param name {@code non-null;} the name of the class whose type + * is desired + * @return {@code non-null;} the corresponding type + * @throws IllegalArgumentException thrown if the name has + * invalid syntax + */ + public static Type internClassName(String name) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (name.startsWith("[")) { + return intern(name); + } + + return intern('L' + name + ';'); + } + + /** + * Constructs an instance corresponding to an "uninitialized type." + * This is a private constructor; use one of the public static + * methods to get instances. + * + * @param descriptor {@code non-null;} the field descriptor for the type + * @param basicType basic type corresponding to this type; one of the + * {@code BT_*} constants + * @param newAt {@code >= -1;} allocation bytecode index + */ + private Type(String descriptor, int basicType, int newAt) { + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + if ((basicType < 0) || (basicType >= BT_COUNT)) { + throw new IllegalArgumentException("bad basicType"); + } + + if (newAt < -1) { + throw new IllegalArgumentException("newAt < -1"); + } + + this.descriptor = descriptor; + this.basicType = basicType; + this.newAt = newAt; + this.arrayType = null; + this.componentType = null; + this.initializedType = null; + } + + /** + * Constructs an instance corresponding to an "initialized type." + * This is a private constructor; use one of the public static + * methods to get instances. + * + * @param descriptor {@code non-null;} the field descriptor for the type + * @param basicType basic type corresponding to this type; one of the + * {@code BT_*} constants + */ + private Type(String descriptor, int basicType) { + this(descriptor, basicType, -1); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + /* + * Since externally-visible types are interned, this check + * helps weed out some easy cases. + */ + return true; + } + + if (!(other instanceof Type)) { + return false; + } + + return descriptor.equals(((Type) other).descriptor); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return descriptor.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public int compareTo(Type other) { + return descriptor.compareTo(other.descriptor); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return descriptor; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + switch (basicType) { + case BT_VOID: return "void"; + case BT_BOOLEAN: return "boolean"; + case BT_BYTE: return "byte"; + case BT_CHAR: return "char"; + case BT_DOUBLE: return "double"; + case BT_FLOAT: return "float"; + case BT_INT: return "int"; + case BT_LONG: return "long"; + case BT_SHORT: return "short"; + case BT_OBJECT: break; + default: return descriptor; + } + + if (isArray()) { + return getComponentType().toHuman() + "[]"; + } + + // Remove the "L...;" around the type and convert "/" to ".". + return getClassName().replace("/", "."); + } + + /** {@inheritDoc} */ + @Override + public Type getType() { + return this; + } + + /** {@inheritDoc} */ + @Override + public Type getFrameType() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_INT: + case BT_SHORT: { + return INT; + } + } + + return this; + } + + /** {@inheritDoc} */ + @Override + public int getBasicType() { + return basicType; + } + + /** {@inheritDoc} */ + @Override + public int getBasicFrameType() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_INT: + case BT_SHORT: { + return BT_INT; + } + } + + return basicType; + } + + /** {@inheritDoc} */ + @Override + public boolean isConstant() { + return false; + } + + /** + * Gets the descriptor. + * + * @return {@code non-null;} the descriptor + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Gets the name of the class this type corresponds to, in internal + * form. This method is only valid if this instance is for a + * normal reference type (that is, a reference type and + * additionally not a return address). + * + * @return {@code non-null;} the internal-form class name + */ + public String getClassName() { + if (className == null) { + if (!isReference()) { + throw new IllegalArgumentException("not an object type: " + + descriptor); + } + + if (descriptor.charAt(0) == '[') { + className = descriptor; + } else { + className = descriptor.substring(1, descriptor.length() - 1); + } + } + + return className; + } + + /** + * Gets the category. Most instances are category 1. {@code long} + * and {@code double} are the only category 2 types. + * + * @see #isCategory1 + * @see #isCategory2 + * @return the category + */ + public int getCategory() { + switch (basicType) { + case BT_LONG: + case BT_DOUBLE: { + return 2; + } + } + + return 1; + } + + /** + * Returns whether or not this is a category 1 type. + * + * @see #getCategory + * @see #isCategory2 + * @return whether or not this is a category 1 type + */ + public boolean isCategory1() { + switch (basicType) { + case BT_LONG: + case BT_DOUBLE: { + return false; + } + } + + return true; + } + + /** + * Returns whether or not this is a category 2 type. + * + * @see #getCategory + * @see #isCategory1 + * @return whether or not this is a category 2 type + */ + public boolean isCategory2() { + switch (basicType) { + case BT_LONG: + case BT_DOUBLE: { + return true; + } + } + + return false; + } + + /** + * Gets whether this type is "intlike." An intlike type is one which, when + * placed on a stack or in a local, is automatically converted to an + * {@code int}. + * + * @return whether this type is "intlike" + */ + public boolean isIntlike() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_INT: + case BT_SHORT: { + return true; + } + } + + return false; + } + + /** + * Gets whether this type is a primitive type. All types are either + * primitive or reference types. + * + * @return whether this type is primitive + */ + public boolean isPrimitive() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_DOUBLE: + case BT_FLOAT: + case BT_INT: + case BT_LONG: + case BT_SHORT: + case BT_VOID: { + return true; + } + } + + return false; + } + + /** + * Gets whether this type is a normal reference type. A normal + * reference type is a reference type that is not a return + * address. This method is just convenient shorthand for + * {@code getBasicType() == Type.BT_OBJECT}. + * + * @return whether this type is a normal reference type + */ + public boolean isReference() { + return (basicType == BT_OBJECT); + } + + /** + * Gets whether this type is an array type. If this method returns + * {@code true}, then it is safe to use {@link #getComponentType} + * to determine the component type. + * + * @return whether this type is an array type + */ + public boolean isArray() { + return (descriptor.charAt(0) == '['); + } + + /** + * Gets whether this type is an array type or is a known-null, and + * hence is compatible with array types. + * + * @return whether this type is an array type + */ + public boolean isArrayOrKnownNull() { + return isArray() || equals(KNOWN_NULL); + } + + /** + * Gets whether this type represents an uninitialized instance. An + * uninitialized instance is what one gets back from the {@code new} + * opcode, and remains uninitialized until a valid constructor is + * invoked on it. + * + * @return whether this type is "uninitialized" + */ + public boolean isUninitialized() { + return (newAt >= 0); + } + + /** + * Gets the bytecode index at which this uninitialized type was + * allocated. This returns {@code Integer.MAX_VALUE} if this + * type is an uninitialized incoming parameter (i.e., the + * {@code this} of an {@code } method) or + * {@code -1} if this type is in fact initialized. + * + * @return {@code >= -1;} the allocation bytecode index + */ + public int getNewAt() { + return newAt; + } + + /** + * Gets the initialized type corresponding to this instance, but only + * if this instance is in fact an uninitialized object type. + * + * @return {@code non-null;} the initialized type + */ + public Type getInitializedType() { + if (initializedType == null) { + throw new IllegalArgumentException("initialized type: " + + descriptor); + } + + return initializedType; + } + + /** + * Gets the type corresponding to an array of this type. + * + * @return {@code non-null;} the array type + */ + public Type getArrayType() { + if (arrayType == null) { + arrayType = putIntern(new Type('[' + descriptor, BT_OBJECT)); + } + + return arrayType; + } + + /** + * Gets the component type of this type. This method is only valid on + * array types. + * + * @return {@code non-null;} the component type + */ + public Type getComponentType() { + if (componentType == null) { + if (descriptor.charAt(0) != '[') { + throw new IllegalArgumentException("not an array type: " + + descriptor); + } + componentType = intern(descriptor.substring(1)); + } + + return componentType; + } + + /** + * Returns a new interned instance which is identical to this one, except + * it is indicated as uninitialized and allocated at the given bytecode + * index. This instance must be an initialized object type. + * + * @param newAt {@code >= 0;} the allocation bytecode index + * @return {@code non-null;} an appropriately-constructed instance + */ + public Type asUninitialized(int newAt) { + if (newAt < 0) { + throw new IllegalArgumentException("newAt < 0"); + } + + if (!isReference()) { + throw new IllegalArgumentException("not a reference type: " + + descriptor); + } + + if (isUninitialized()) { + /* + * Dealing with uninitialized types as a starting point is + * a pain, and it's not clear that it'd ever be used, so + * just disallow it. + */ + throw new IllegalArgumentException("already uninitialized: " + + descriptor); + } + + /* + * Create a new descriptor that is unique and shouldn't conflict + * with "normal" type descriptors + */ + String newDesc = 'N' + Hex.u2(newAt) + descriptor; + Type result = new Type(newDesc, BT_OBJECT, newAt); + result.initializedType = this; + return putIntern(result); + } + + /** + * Puts the given instance in the intern table if it's not already + * there. If a conflicting value is already in the table, then leave it. + * Return the interned value. + * + * @param type {@code non-null;} instance to make interned + * @return {@code non-null;} the actual interned object + */ + private static Type putIntern(Type type) { + Type result = internTable.putIfAbsent(type.getDescriptor(), type); + return result != null ? result : type; + } + + public static void clearInternTable() { + internTable.clear(); + initInterns(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/rop/type/TypeBearer.java b/dexlib/src/main/java/com/android/dx/rop/type/TypeBearer.java new file mode 100644 index 000000000..b03dbafa5 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/type/TypeBearer.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.type; + +import com.android.dx.util.ToHuman; + +/** + * Object which has an associated type, possibly itself. + */ +public interface TypeBearer + extends ToHuman { + /** + * Gets the type associated with this instance. + * + * @return {@code non-null;} the type + */ + public Type getType(); + + /** + * Gets the frame type corresponding to this type. This method returns + * {@code this}, except if {@link Type#isIntlike} on the underlying + * type returns {@code true} but the underlying type is not in + * fact {@link Type#INT}, in which case this method returns an instance + * whose underlying type is {@code INT}. + * + * @return {@code non-null;} the frame type for this instance + */ + public TypeBearer getFrameType(); + + /** + * Gets the basic type corresponding to this instance. + * + * @return the basic type; one of the {@code BT_*} constants + * defined by {@link Type} + */ + public int getBasicType(); + + /** + * Gets the basic type corresponding to this instance's frame type. This + * is equivalent to {@code getFrameType().getBasicType()}, and + * is the same as calling {@code getFrameType()} unless this + * instance is an int-like type, in which case this method returns + * {@code BT_INT}. + * + * @see #getBasicType + * @see #getFrameType + * + * @return the basic frame type; one of the {@code BT_*} constants + * defined by {@link Type} + */ + public int getBasicFrameType(); + + /** + * Returns whether this instance represents a constant value. + * + * @return {@code true} if this instance represents a constant value + * and {@code false} if not + */ + public boolean isConstant(); +} diff --git a/dexlib/src/main/java/com/android/dx/rop/type/TypeList.java b/dexlib/src/main/java/com/android/dx/rop/type/TypeList.java new file mode 100644 index 000000000..de2d62e29 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/type/TypeList.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.rop.type; + +/** + * List of {@link Type} instances (or of things that contain types). + */ +public interface TypeList { + /** + * Returns whether this instance is mutable. Note that the + * {@code TypeList} interface itself doesn't provide any + * means of mutation, but that doesn't mean that there isn't an + * extra-interface way of mutating an instance. + * + * @return {@code true} if this instance is mutable or + * {@code false} if it is immutable + */ + public boolean isMutable(); + + /** + * Gets the size of this list. + * + * @return {@code >= 0;} the size + */ + public int size(); + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public Type getType(int n); + + /** + * Gets the number of 32-bit words required to hold instances of + * all the elements of this list. This is a sum of the widths (categories) + * of all the elements. + * + * @return {@code >= 0;} the required number of words + */ + public int getWordCount(); + + /** + * Returns a new instance which is identical to this one, except that + * the given item is appended to the end and it is guaranteed to be + * immutable. + * + * @param type {@code non-null;} item to append + * @return {@code non-null;} an appropriately-constructed instance + */ + public TypeList withAddedType(Type type); +} diff --git a/dexlib/src/main/java/com/android/dx/rop/type/package.html b/dexlib/src/main/java/com/android/dx/rop/type/package.html new file mode 100644 index 000000000..93d9d5f0b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/rop/type/package.html @@ -0,0 +1,8 @@ + +

      Implementation of classes that represent types (classes or primitives).

      + +

      PACKAGES USED: +

        +
      • com.android.dx.util
      • +
      + diff --git a/dexlib/src/main/java/com/android/dx/ssa/BasicRegisterMapper.java b/dexlib/src/main/java/com/android/dx/ssa/BasicRegisterMapper.java new file mode 100644 index 000000000..45e93c347 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/BasicRegisterMapper.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.util.IntList; + +/** + * This class maps one register space into another, with + * each mapping built up individually and added via addMapping() + */ +public class BasicRegisterMapper extends RegisterMapper { + /** indexed by old register, containing new name */ + private IntList oldToNew; + + /** running count of used registers in new namespace */ + private int runningCountNewRegisters; + + /** + * Creates a new OneToOneRegisterMapper. + * + * @param countOldRegisters the number of registers in the old name space + */ + public BasicRegisterMapper(int countOldRegisters) { + oldToNew = new IntList(countOldRegisters); + } + + /** {@inheritDoc} */ + @Override + public int getNewRegisterCount() { + return runningCountNewRegisters; + } + + /** {@inheritDoc} */ + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec == null) { + return null; + } + + int newReg; + try { + newReg = oldToNew.get(registerSpec.getReg()); + } catch (IndexOutOfBoundsException ex) { + newReg = -1; + } + + if (newReg < 0) { + throw new RuntimeException("no mapping specified for register"); + } + + return registerSpec.withReg(newReg); + } + + /** + * Returns the new-namespace mapping for the specified + * old-namespace register, or -1 if one exists. + * + * @param oldReg {@code >= 0;} old-namespace register + * @return new-namespace register or -1 if none + */ + public int oldToNew(int oldReg) { + if (oldReg >= oldToNew.size()) { + return -1; + } + + return oldToNew.get(oldReg); + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append("Old\tNew\n"); + int sz = oldToNew.size(); + + for (int i = 0; i < sz; i++) { + sb.append(i); + sb.append('\t'); + sb.append(oldToNew.get(i)); + sb.append('\n'); + } + + sb.append("new reg count:"); + + sb.append(runningCountNewRegisters); + sb.append('\n'); + + return sb.toString(); + } + + /** + * Adds a mapping to the mapper. If oldReg has already been mapped, + * overwrites previous mapping with new mapping. + * + * @param oldReg {@code >= 0;} old register + * @param newReg {@code >= 0;} new register + * @param category {@code 1..2;} width of reg + */ + public void addMapping(int oldReg, int newReg, int category) { + if (oldReg >= oldToNew.size()) { + // expand the array as necessary + for (int i = oldReg - oldToNew.size(); i >= 0; i--) { + oldToNew.add(-1); + } + } + + oldToNew.set(oldReg, newReg); + + if (runningCountNewRegisters < (newReg + category)) { + runningCountNewRegisters = newReg + category; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/ConstCollector.java b/dexlib/src/main/java/com/android/dx/ssa/ConstCollector.java new file mode 100644 index 000000000..905554433 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/ConstCollector.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.PlainCstInsn; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.code.ThrowingCstInsn; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.TypedConstant; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.TypeBearer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/** + * Collects constants that are used more than once at the top of the + * method block. This increases register usage by about 5% but decreases + * insn size by about 3%. + */ +public class ConstCollector { + /** Maximum constants to collect per method. Puts cap on reg use */ + private static final int MAX_COLLECTED_CONSTANTS = 5; + + /** + * Also collect string consts, although they can throw exceptions. + * This is off now because it just doesn't seem to gain a whole lot. + * TODO if you turn this on, you must change SsaInsn.hasSideEffect() + * to return false for const-string insns whose exceptions are not + * caught in the current method. + */ + private static final boolean COLLECT_STRINGS = false; + + /** + * If true, allow one local var to be involved with a collected const. + * Turned off because it mostly just inserts more moves. + */ + private static final boolean COLLECT_ONE_LOCAL = false; + + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** + * Processes a method. + * + * @param ssaMethod {@code non-null;} method to process + */ + public static void process(SsaMethod ssaMethod) { + ConstCollector cc = new ConstCollector(ssaMethod); + cc.run(); + } + + /** + * Constructs an instance. + * + * @param ssaMethod {@code non-null;} method to process + */ + private ConstCollector(SsaMethod ssaMethod) { + this.ssaMeth = ssaMethod; + } + + /** + * Applies the optimization. + */ + private void run() { + int regSz = ssaMeth.getRegCount(); + + ArrayList constantList + = getConstsSortedByCountUse(); + + int toCollect = Math.min(constantList.size(), MAX_COLLECTED_CONSTANTS); + + SsaBasicBlock start = ssaMeth.getEntryBlock(); + + // Constant to new register containing the constant + HashMap newRegs + = new HashMap (toCollect); + + for (int i = 0; i < toCollect; i++) { + TypedConstant cst = constantList.get(i); + RegisterSpec result + = RegisterSpec.make(ssaMeth.makeNewSsaReg(), cst); + + Rop constRop = Rops.opConst(cst); + + if (constRop.getBranchingness() == Rop.BRANCH_NONE) { + start.addInsnToHead( + new PlainCstInsn(Rops.opConst(cst), + SourcePosition.NO_INFO, result, + RegisterSpecList.EMPTY, cst)); + } else { + // We need two new basic blocks along with the new insn + SsaBasicBlock entryBlock = ssaMeth.getEntryBlock(); + SsaBasicBlock successorBlock + = entryBlock.getPrimarySuccessor(); + + // Insert a block containing the const insn. + SsaBasicBlock constBlock + = entryBlock.insertNewSuccessor(successorBlock); + + constBlock.replaceLastInsn( + new ThrowingCstInsn(constRop, SourcePosition.NO_INFO, + RegisterSpecList.EMPTY, + StdTypeList.EMPTY, cst)); + + // Insert a block containing the move-result-pseudo insn. + + SsaBasicBlock resultBlock + = constBlock.insertNewSuccessor(successorBlock); + PlainInsn insn + = new PlainInsn( + Rops.opMoveResultPseudo(result.getTypeBearer()), + SourcePosition.NO_INFO, + result, RegisterSpecList.EMPTY); + + resultBlock.addInsnToHead(insn); + } + + newRegs.put(cst, result); + } + + updateConstUses(newRegs, regSz); + } + + /** + * Gets all of the collectable constant values used in this method, + * sorted by most used first. Skips non-collectable consts, such as + * non-string object constants + * + * @return {@code non-null;} list of constants in most-to-least used order + */ + private ArrayList getConstsSortedByCountUse() { + int regSz = ssaMeth.getRegCount(); + + final HashMap countUses + = new HashMap(); + + /* + * Each collected constant can be used by just one local + * (used only if COLLECT_ONE_LOCAL is true). + */ + final HashSet usedByLocal + = new HashSet(); + + // Count how many times each const value is used. + for (int i = 0; i < regSz; i++) { + SsaInsn insn = ssaMeth.getDefinitionForRegister(i); + + if (insn == null || insn.getOpcode() == null) continue; + + RegisterSpec result = insn.getResult(); + TypeBearer typeBearer = result.getTypeBearer(); + + if (!typeBearer.isConstant()) continue; + + TypedConstant cst = (TypedConstant) typeBearer; + + // Find defining instruction for move-result-pseudo instructions + if (insn.getOpcode().getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + int pred = insn.getBlock().getPredecessors().nextSetBit(0); + ArrayList predInsns; + predInsns = ssaMeth.getBlocks().get(pred).getInsns(); + insn = predInsns.get(predInsns.size()-1); + } + + if (insn.canThrow()) { + /* + * Don't move anything other than strings -- the risk + * of changing where an exception is thrown is too high. + */ + if (!(cst instanceof CstString) || !COLLECT_STRINGS) { + continue; + } + /* + * We can't move any throwable const whose throw will be + * caught, so don't count them. + */ + if (insn.getBlock().getSuccessors().cardinality() > 1) { + continue; + } + } + + /* + * TODO: Might be nice to try and figure out which local + * wins most when collected. + */ + if (ssaMeth.isRegALocal(result)) { + if (!COLLECT_ONE_LOCAL) { + continue; + } else { + if (usedByLocal.contains(cst)) { + // Count one local usage only. + continue; + } else { + usedByLocal.add(cst); + } + } + } + + Integer has = countUses.get(cst); + if (has == null) { + countUses.put(cst, 1); + } else { + countUses.put(cst, has + 1); + } + } + + // Collect constants that have been reused. + ArrayList constantList = new ArrayList(); + for (Map.Entry entry : countUses.entrySet()) { + if (entry.getValue() > 1) { + constantList.add(entry.getKey()); + } + } + + // Sort by use, with most used at the beginning of the list. + Collections.sort(constantList, new Comparator() { + public int compare(Constant a, Constant b) { + int ret; + ret = countUses.get(b) - countUses.get(a); + + if (ret == 0) { + /* + * Provide sorting determinisim for constants with same + * usage count. + */ + ret = a.compareTo(b); + } + + return ret; + } + + @Override + public boolean equals (Object obj) { + return obj == this; + } + }); + + return constantList; + } + + /** + * Inserts mark-locals if necessary when changing a register. If + * the definition of {@code origReg} is associated with a local + * variable, then insert a mark-local for {@code newReg} just below + * it. We expect the definition of {@code origReg} to ultimately + * be removed by the dead code eliminator + * + * @param origReg {@code non-null;} original register + * @param newReg {@code non-null;} new register that will replace + * {@code origReg} + */ + private void fixLocalAssignment(RegisterSpec origReg, + RegisterSpec newReg) { + for (SsaInsn use : ssaMeth.getUseListForRegister(origReg.getReg())) { + RegisterSpec localAssignment = use.getLocalAssignment(); + if (localAssignment == null) { + continue; + } + + if (use.getResult() == null) { + /* + * This is a mark-local. it will be updated when all uses + * are updated. + */ + continue; + } + + LocalItem local = localAssignment.getLocalItem(); + + // Un-associate original use. + use.setResultLocal(null); + + // Now add a mark-local to the new reg immediately after. + newReg = newReg.withLocalItem(local); + + SsaInsn newInsn + = SsaInsn.makeFromRop( + new PlainInsn(Rops.opMarkLocal(newReg), + SourcePosition.NO_INFO, null, + RegisterSpecList.make(newReg)), + use.getBlock()); + + ArrayList insns = use.getBlock().getInsns(); + + insns.add(insns.indexOf(use) + 1, newInsn); + } + } + + /** + * Updates all uses of various consts to use the values in the newly + * assigned registers. + * + * @param newRegs {@code non-null;} mapping between constant and new reg + * @param origRegCount {@code >=0;} original SSA reg count, not including + * newly added constant regs + */ + private void updateConstUses(HashMap newRegs, + int origRegCount) { + + /* + * set of constants associated with a local variable; used + * only if COLLECT_ONE_LOCAL is true. + */ + final HashSet usedByLocal + = new HashSet(); + + final ArrayList[] useList = ssaMeth.getUseListCopy(); + + for (int i = 0; i < origRegCount; i++) { + SsaInsn insn = ssaMeth.getDefinitionForRegister(i); + + if (insn == null) { + continue; + } + + final RegisterSpec origReg = insn.getResult(); + TypeBearer typeBearer = insn.getResult().getTypeBearer(); + + if (!typeBearer.isConstant()) continue; + + TypedConstant cst = (TypedConstant) typeBearer; + final RegisterSpec newReg = newRegs.get(cst); + + if (newReg == null) { + continue; + } + + if (ssaMeth.isRegALocal(origReg)) { + if (!COLLECT_ONE_LOCAL) { + continue; + } else { + /* + * TODO: If the same local gets the same cst + * multiple times, it would be nice to reuse the + * register. + */ + if (usedByLocal.contains(cst)) { + continue; + } else { + usedByLocal.add(cst); + fixLocalAssignment(origReg, newRegs.get(cst)); + } + } + } + + // maps an original const register to the new collected register + RegisterMapper mapper = new RegisterMapper() { + @Override + public int getNewRegisterCount() { + return ssaMeth.getRegCount(); + } + + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec.getReg() == origReg.getReg()) { + return newReg.withLocalItem( + registerSpec.getLocalItem()); + } + + return registerSpec; + } + }; + + for (SsaInsn use : useList[origReg.getReg()]) { + if (use.canThrow() + && use.getBlock().getSuccessors().cardinality() > 1) { + continue; + } + use.mapSourceRegisters(mapper); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/DeadCodeRemover.java b/dexlib/src/main/java/com/android/dx/ssa/DeadCodeRemover.java new file mode 100644 index 000000000..4a2f59d04 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/DeadCodeRemover.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; + +/** + * A variation on Appel Algorithm 19.12 "Dead code elimination in SSA form". + * + * TODO this algorithm is more efficient if run in reverse from exit + * block to entry block. + */ +public class DeadCodeRemover { + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** ssaMeth.getRegCount() */ + private final int regCount; + + /** + * indexed by register: whether reg should be examined + * (does it correspond to a no-side-effect insn?) + */ + private final BitSet worklist; + + /** use list indexed by register; modified during operation */ + private final ArrayList[] useList; + + /** + * Process a method with the dead-code remver + * + * @param ssaMethod method to process + */ + public static void process(SsaMethod ssaMethod) { + DeadCodeRemover dc = new DeadCodeRemover(ssaMethod); + dc.run(); + } + + /** + * Constructs an instance. + * + * @param ssaMethod method to process + */ + private DeadCodeRemover(SsaMethod ssaMethod) { + this.ssaMeth = ssaMethod; + + regCount = ssaMethod.getRegCount(); + worklist = new BitSet(regCount); + useList = ssaMeth.getUseListCopy(); + } + + /** + * Runs the dead code remover. + */ + private void run() { + pruneDeadInstructions(); + + HashSet deletedInsns = new HashSet(); + + ssaMeth.forEachInsn(new NoSideEffectVisitor(worklist)); + + int regV; + + while ( 0 <= (regV = worklist.nextSetBit(0)) ) { + worklist.clear(regV); + + if (useList[regV].size() == 0 + || isCircularNoSideEffect(regV, null)) { + + SsaInsn insnS = ssaMeth.getDefinitionForRegister(regV); + + // This insn has already been deleted. + if (deletedInsns.contains(insnS)) { + continue; + } + + RegisterSpecList sources = insnS.getSources(); + + int sz = sources.size(); + for (int i = 0; i < sz; i++) { + // Delete this insn from all usage lists. + RegisterSpec source = sources.get(i); + useList[source.getReg()].remove(insnS); + + if (!hasSideEffect( + ssaMeth.getDefinitionForRegister( + source.getReg()))) { + /* + * Only registers whose definition has no side effect + * should be added back to the worklist. + */ + worklist.set(source.getReg()); + } + } + + // Schedule this insn for later deletion. + deletedInsns.add(insnS); + } + } + + ssaMeth.deleteInsns(deletedInsns); + } + + /** + * Removes all instructions from every unreachable block. + */ + private void pruneDeadInstructions() { + HashSet deletedInsns = new HashSet(); + + ssaMeth.computeReachability(); + + for (SsaBasicBlock block : ssaMeth.getBlocks()) { + if (block.isReachable()) continue; + + // Prune instructions from unreachable blocks + for (int i = 0; i < block.getInsns().size(); i++) { + SsaInsn insn = block.getInsns().get(i); + RegisterSpecList sources = insn.getSources(); + int sourcesSize = sources.size(); + + // Delete this instruction completely if it has sources + if (sourcesSize != 0) { + deletedInsns.add(insn); + } + + // Delete this instruction from all usage lists. + for (int j = 0; j < sourcesSize; j++) { + RegisterSpec source = sources.get(j); + useList[source.getReg()].remove(insn); + } + + // Remove this instruction result from the sources of any phis + RegisterSpec result = insn.getResult(); + if (result == null) continue; + for (SsaInsn use : useList[result.getReg()]) { + if (use instanceof PhiInsn) { + PhiInsn phiUse = (PhiInsn) use; + phiUse.removePhiRegister(result); + } + } + } + } + + ssaMeth.deleteInsns(deletedInsns); + } + + /** + * Returns true if the only uses of this register form a circle of + * operations with no side effects. + * + * @param regV register to examine + * @param set a set of registers that we've already determined + * are only used as sources in operations with no side effect or null + * if this is the first recursion + * @return true if usage is circular without side effect + */ + private boolean isCircularNoSideEffect(int regV, BitSet set) { + if ((set != null) && set.get(regV)) { + return true; + } + + for (SsaInsn use : useList[regV]) { + if (hasSideEffect(use)) { + return false; + } + } + + if (set == null) { + set = new BitSet(regCount); + } + + // This register is only used in operations that have no side effect. + set.set(regV); + + for (SsaInsn use : useList[regV]) { + RegisterSpec result = use.getResult(); + + if (result == null + || !isCircularNoSideEffect(result.getReg(), set)) { + return false; + } + } + + return true; + } + + /** + * Returns true if this insn has a side-effect. Returns true + * if the insn is null for reasons stated in the code block. + * + * @param insn {@code null-ok;} instruction in question + * @return true if it has a side-effect + */ + private static boolean hasSideEffect(SsaInsn insn) { + if (insn == null) { + /* While false would seem to make more sense here, true + * prevents us from adding this back to a worklist unnecessarally. + */ + return true; + } + + return insn.hasSideEffect(); + } + + /** + * A callback class used to build up the initial worklist of + * registers defined by an instruction with no side effect. + */ + static private class NoSideEffectVisitor implements SsaInsn.Visitor { + BitSet noSideEffectRegs; + + /** + * Passes in data structures that will be filled out after + * ssaMeth.forEachInsn() is called with this instance. + * + * @param noSideEffectRegs to-build bitset of regs that are + * results of regs with no side effects + */ + public NoSideEffectVisitor(BitSet noSideEffectRegs) { + this.noSideEffectRegs = noSideEffectRegs; + } + + /** {@inheritDoc} */ + public void visitMoveInsn (NormalSsaInsn insn) { + // If we're tracking local vars, some moves have side effects. + if (!hasSideEffect(insn)) { + noSideEffectRegs.set(insn.getResult().getReg()); + } + } + + /** {@inheritDoc} */ + public void visitPhiInsn (PhiInsn phi) { + // If we're tracking local vars, then some phis have side effects. + if (!hasSideEffect(phi)) { + noSideEffectRegs.set(phi.getResult().getReg()); + } + } + + /** {@inheritDoc} */ + public void visitNonMoveInsn (NormalSsaInsn insn) { + RegisterSpec result = insn.getResult(); + if (!hasSideEffect(insn) && result != null) { + noSideEffectRegs.set(result.getReg()); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/DomFront.java b/dexlib/src/main/java/com/android/dx/ssa/DomFront.java new file mode 100644 index 000000000..0278a6783 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/DomFront.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.util.IntSet; +import java.util.ArrayList; +import java.util.BitSet; + +/** + * Calculates the dominance-frontiers of a method's basic blocks. + * Algorithm from "A Simple, Fast Dominance Algorithm" by Cooper, + * Harvey, and Kennedy; transliterated to Java. + */ +public class DomFront { + /** local debug flag */ + private static final boolean DEBUG = false; + + /** {@code non-null;} method being processed */ + private final SsaMethod meth; + + private final ArrayList nodes; + + private final DomInfo[] domInfos; + + /** + * Dominance-frontier information for a single basic block. + */ + public static class DomInfo { + /** + * {@code null-ok;} the dominance frontier set indexed by + * block index + */ + public IntSet dominanceFrontiers; + + /** {@code >= 0 after run();} the index of the immediate dominator */ + public int idom = -1; + } + + /** + * Constructs instance. Call {@link DomFront#run} to process. + * + * @param meth {@code non-null;} method to process + */ + public DomFront(SsaMethod meth) { + this.meth = meth; + nodes = meth.getBlocks(); + + int szNodes = nodes.size(); + domInfos = new DomInfo[szNodes]; + + for (int i = 0; i < szNodes; i++) { + domInfos[i] = new DomInfo(); + } + } + + /** + * Calculates the dominance frontier information for the method. + * + * @return {@code non-null;} an array of DomInfo structures + */ + public DomInfo[] run() { + int szNodes = nodes.size(); + + if (DEBUG) { + for (int i = 0; i < szNodes; i++) { + SsaBasicBlock node = nodes.get(i); + System.out.println("pred[" + i + "]: " + + node.getPredecessors()); + } + } + + Dominators methDom = Dominators.make(meth, domInfos, false); + + if (DEBUG) { + for (int i = 0; i < szNodes; i++) { + DomInfo info = domInfos[i]; + System.out.println("idom[" + i + "]: " + + info.idom); + } + } + + buildDomTree(); + + if (DEBUG) { + debugPrintDomChildren(); + } + + for (int i = 0; i < szNodes; i++) { + domInfos[i].dominanceFrontiers + = SetFactory.makeDomFrontSet(szNodes); + } + + calcDomFronts(); + + if (DEBUG) { + for (int i = 0; i < szNodes; i++) { + System.out.println("df[" + i + "]: " + + domInfos[i].dominanceFrontiers); + } + } + + return domInfos; + } + + private void debugPrintDomChildren() { + int szNodes = nodes.size(); + + for (int i = 0; i < szNodes; i++) { + SsaBasicBlock node = nodes.get(i); + StringBuffer sb = new StringBuffer(); + + sb.append('{'); + boolean comma = false; + for (SsaBasicBlock child : node.getDomChildren()) { + if (comma) { + sb.append(','); + } + sb.append(child); + comma = true; + } + sb.append('}'); + + System.out.println("domChildren[" + node + "]: " + + sb); + } + } + + /** + * The dominators algorithm leaves us knowing who the immediate dominator + * is for each node. This sweeps the node list and builds the proper + * dominance tree. + */ + private void buildDomTree() { + int szNodes = nodes.size(); + + for (int i = 0; i < szNodes; i++) { + DomInfo info = domInfos[i]; + + if (info.idom == -1) continue; + + SsaBasicBlock domParent = nodes.get(info.idom); + domParent.addDomChild(nodes.get(i)); + } + } + + /** + * Calculates the dominance-frontier set. + * from "A Simple, Fast Dominance Algorithm" by Cooper, + * Harvey, and Kennedy; transliterated to Java. + */ + private void calcDomFronts() { + int szNodes = nodes.size(); + + for (int b = 0; b < szNodes; b++) { + SsaBasicBlock nb = nodes.get(b); + DomInfo nbInfo = domInfos[b]; + BitSet pred = nb.getPredecessors(); + + if (pred.cardinality() > 1) { + for (int i = pred.nextSetBit(0); i >= 0; + i = pred.nextSetBit(i + 1)) { + + for (int runnerIndex = i; + runnerIndex != nbInfo.idom; /* empty */) { + /* + * We can stop if we hit a block we already + * added label to, since we must be at a part + * of the dom tree we have seen before. + */ + if (runnerIndex == -1) break; + + DomInfo runnerInfo = domInfos[runnerIndex]; + + if (runnerInfo.dominanceFrontiers.has(b)) { + break; + } + + // Add b to runner's dominance frontier set. + runnerInfo.dominanceFrontiers.add(b); + runnerIndex = runnerInfo.idom; + } + } + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/Dominators.java b/dexlib/src/main/java/com/android/dx/ssa/Dominators.java new file mode 100644 index 000000000..bffea616c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/Dominators.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; + +/** + * This class computes dominator and post-dominator information using the + * Lengauer-Tarjan method. + * + * See A Fast Algorithm for Finding Dominators in a Flowgraph + * T. Lengauer & R. Tarjan, ACM TOPLAS July 1979, pgs 121-141. + * + * This implementation runs in time O(n log n). The time bound + * could be changed to O(n * ack(n)) with a small change to the link and eval, + * and an addition of a child field to the DFS info. In reality, the constant + * overheads are high enough that the current method is faster in all but the + * strangest artificially constructed examples. + * + * The basic idea behind this algorithm is to perform a DFS walk, keeping track + * of various info about parents. We then use this info to calculate the + * dominators, using union-find structures to link together the DFS info, + * then finally evaluate the union-find results to get the dominators. + * This implementation is m log n because it does not perform union by + * rank to keep the union-find tree balanced. + */ +public final class Dominators { + /* postdom is true if we want post dominators */ + private final boolean postdom; + + /* {@code non-null;} method being processed */ + private final SsaMethod meth; + + /* Method's basic blocks. */ + private final ArrayList blocks; + + /** indexed by basic block index */ + private final DFSInfo[] info; + + private final ArrayList vertex; + + /** {@code non-null;} the raw dominator info */ + private final DomFront.DomInfo domInfos[]; + + /** + * Constructs an instance. + * + * @param meth {@code non-null;} method to process + * @param domInfos {@code non-null;} the raw dominator info + * @param postdom true for postdom information, false for normal dom info + */ + private Dominators(SsaMethod meth, DomFront.DomInfo[] domInfos, + boolean postdom) { + this.meth = meth; + this.domInfos = domInfos; + this.postdom = postdom; + this.blocks = meth.getBlocks(); + this.info = new DFSInfo[blocks.size() + 2]; + this.vertex = new ArrayList(); + } + + /** + * Constructs a fully-initialized instance. (This method exists so as + * to avoid calling a large amount of code in the constructor.) + * + * @param meth {@code non-null;} method to process + * @param domInfos {@code non-null;} the raw dominator info + * @param postdom true for postdom information, false for normal dom info + */ + public static Dominators make(SsaMethod meth, DomFront.DomInfo[] domInfos, + boolean postdom) { + Dominators result = new Dominators(meth, domInfos, postdom); + + result.run(); + return result; + } + + private BitSet getSuccs(SsaBasicBlock block) { + if (postdom) { + return block.getPredecessors(); + } else { + return block.getSuccessors(); + } + } + + private BitSet getPreds(SsaBasicBlock block) { + if (postdom) { + return block.getSuccessors(); + } else { + return block.getPredecessors(); + } + } + + /** + * Performs path compress on the DFS info. + * + * @param in Basic block whose DFS info we are path compressing. + */ + private void compress(SsaBasicBlock in) { + DFSInfo bbInfo = info[in.getIndex()]; + DFSInfo ancestorbbInfo = info[bbInfo.ancestor.getIndex()]; + + if (ancestorbbInfo.ancestor != null) { + ArrayList worklist = new ArrayList(); + HashSet visited = new HashSet(); + worklist.add(in); + + while (!worklist.isEmpty()) { + int wsize = worklist.size(); + SsaBasicBlock v = worklist.get(wsize - 1); + DFSInfo vbbInfo = info[v.getIndex()]; + SsaBasicBlock vAncestor = vbbInfo.ancestor; + DFSInfo vabbInfo = info[vAncestor.getIndex()]; + + // Make sure we process our ancestor before ourselves. + if (visited.add(vAncestor) && vabbInfo.ancestor != null) { + worklist.add(vAncestor); + continue; + } + worklist.remove(wsize - 1); + + // Update based on ancestor info. + if (vabbInfo.ancestor == null) { + continue; + } + SsaBasicBlock vAncestorRep = vabbInfo.rep; + SsaBasicBlock vRep = vbbInfo.rep; + if (info[vAncestorRep.getIndex()].semidom + < info[vRep.getIndex()].semidom) { + vbbInfo.rep = vAncestorRep; + } + vbbInfo.ancestor = vabbInfo.ancestor; + } + } + } + + private SsaBasicBlock eval(SsaBasicBlock v) { + DFSInfo bbInfo = info[v.getIndex()]; + + if (bbInfo.ancestor == null) { + return v; + } + + compress(v); + return bbInfo.rep; + } + + /** + * Performs dominator/post-dominator calculation for the control + * flow graph. + * + * @param meth {@code non-null;} method to analyze + */ + private void run() { + SsaBasicBlock root = postdom + ? meth.getExitBlock() : meth.getEntryBlock(); + + if (root != null) { + vertex.add(root); + domInfos[root.getIndex()].idom = root.getIndex(); + } + + /* + * First we perform a DFS numbering of the blocks, by + * numbering the dfs tree roots. + */ + + DfsWalker walker = new DfsWalker(); + meth.forEachBlockDepthFirst(postdom, walker); + + // the largest semidom number assigned + int dfsMax = vertex.size() - 1; + + // Now calculate semidominators. + for (int i = dfsMax; i >= 2; --i) { + SsaBasicBlock w = vertex.get(i); + DFSInfo wInfo = info[w.getIndex()]; + + BitSet preds = getPreds(w); + for (int j = preds.nextSetBit(0); + j >= 0; + j = preds.nextSetBit(j + 1)) { + SsaBasicBlock predBlock = blocks.get(j); + DFSInfo predInfo = info[predBlock.getIndex()]; + + /* + * PredInfo may not exist in case the predecessor is + * not reachable. + */ + if (predInfo != null) { + int predSemidom = info[eval(predBlock).getIndex()].semidom; + if (predSemidom < wInfo.semidom) { + wInfo.semidom = predSemidom; + } + } + } + info[vertex.get(wInfo.semidom).getIndex()].bucket.add(w); + + /* + * Normally we would call link here, but in our O(m log n) + * implementation this is equivalent to the following + * single line. + */ + wInfo.ancestor = wInfo.parent; + + // Implicity define idom for each vertex. + ArrayList wParentBucket; + wParentBucket = info[wInfo.parent.getIndex()].bucket; + + while (!wParentBucket.isEmpty()) { + int lastItem = wParentBucket.size() - 1; + SsaBasicBlock last = wParentBucket.remove(lastItem); + SsaBasicBlock U = eval(last); + if (info[U.getIndex()].semidom + < info[last.getIndex()].semidom) { + domInfos[last.getIndex()].idom = U.getIndex(); + } else { + domInfos[last.getIndex()].idom = wInfo.parent.getIndex(); + } + } + } + + // Now explicitly define the immediate dominator of each vertex + for (int i = 2; i <= dfsMax; ++i) { + SsaBasicBlock w = vertex.get(i); + if (domInfos[w.getIndex()].idom + != vertex.get(info[w.getIndex()].semidom).getIndex()) { + domInfos[w.getIndex()].idom + = domInfos[domInfos[w.getIndex()].idom].idom; + } + } + } + + /** + * Callback for depth-first walk through control flow graph (either + * from the entry block or the exit block). Records the traversal order + * in the {@code info}list. + */ + private class DfsWalker implements SsaBasicBlock.Visitor { + private int dfsNum = 0; + + public void visitBlock(SsaBasicBlock v, SsaBasicBlock parent) { + DFSInfo bbInfo = new DFSInfo(); + bbInfo.semidom = ++dfsNum; + bbInfo.rep = v; + bbInfo.parent = parent; + vertex.add(v); + info[v.getIndex()] = bbInfo; + } + } + + private static final class DFSInfo { + public int semidom; + public SsaBasicBlock parent; + + /** + * rep(resentative) is known as "label" in the paper. It is the node + * that our block's DFS info has been unioned to. + */ + public SsaBasicBlock rep; + + public SsaBasicBlock ancestor; + public ArrayList bucket; + + public DFSInfo() { + bucket = new ArrayList(); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/EscapeAnalysis.java b/dexlib/src/main/java/com/android/dx/ssa/EscapeAnalysis.java new file mode 100644 index 000000000..8533de834 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/EscapeAnalysis.java @@ -0,0 +1,842 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.Exceptions; +import com.android.dx.rop.code.FillArrayDataInsn; +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.PlainCstInsn; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.ThrowingCstInsn; +import com.android.dx.rop.code.ThrowingInsn; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.rop.cst.CstMethodRef; +import com.android.dx.rop.cst.CstNat; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.CstType; +import com.android.dx.rop.cst.TypedConstant; +import com.android.dx.rop.cst.Zeroes; +import com.android.dx.rop.type.StdTypeList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; +import java.util.List; + +/** + * Simple intraprocedural escape analysis. Finds new arrays that don't escape + * the method they are created in and replaces the array values with registers. + */ +public class EscapeAnalysis { + /** + * Struct used to generate and maintain escape analysis results. + */ + static class EscapeSet { + /** set containing all registers related to an object */ + BitSet regSet; + /** escape state of the object */ + EscapeState escape; + /** list of objects that are put into this object */ + ArrayList childSets; + /** list of objects that this object is put into */ + ArrayList parentSets; + /** flag to indicate this object is a scalar replaceable array */ + boolean replaceableArray; + + /** + * Constructs an instance of an EscapeSet + * + * @param reg the SSA register that defines the object + * @param size the number of registers in the method + * @param escState the lattice value to initially set this to + */ + EscapeSet(int reg, int size, EscapeState escState) { + regSet = new BitSet(size); + regSet.set(reg); + escape = escState; + childSets = new ArrayList(); + parentSets = new ArrayList(); + replaceableArray = false; + } + } + + /** + * Lattice values used to indicate escape state for an object. Analysis can + * only raise escape state values, not lower them. + * + * TOP - Used for objects that haven't been analyzed yet + * NONE - Object does not escape, and is eligible for scalar replacement. + * METHOD - Object remains local to method, but can't be scalar replaced. + * INTER - Object is passed between methods. (treated as globally escaping + * since this is an intraprocedural analysis) + * GLOBAL - Object escapes globally. + */ + public enum EscapeState { + TOP, NONE, METHOD, INTER, GLOBAL + } + + /** method we're processing */ + private SsaMethod ssaMeth; + /** ssaMeth.getRegCount() */ + private int regCount; + /** Lattice values for each object register group */ + private ArrayList latticeValues; + + /** + * Constructs an instance. + * + * @param ssaMeth method to process + */ + private EscapeAnalysis(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + this.regCount = ssaMeth.getRegCount(); + this.latticeValues = new ArrayList(); + } + + /** + * Finds the index in the lattice for a particular register. + * Returns the size of the lattice if the register wasn't found. + * + * @param reg {@code non-null;} register being looked up + * @return index of the register or size of the lattice if it wasn't found. + */ + private int findSetIndex(RegisterSpec reg) { + int i; + for (i = 0; i < latticeValues.size(); i++) { + EscapeSet e = latticeValues.get(i); + if (e.regSet.get(reg.getReg())) { + return i; + } + } + return i; + } + + /** + * Finds the corresponding instruction for a given move result + * + * @param moveInsn {@code non-null;} a move result instruction + * @return {@code non-null;} the instruction that produces the result for + * the move + */ + private SsaInsn getInsnForMove(SsaInsn moveInsn) { + int pred = moveInsn.getBlock().getPredecessors().nextSetBit(0); + ArrayList predInsns = ssaMeth.getBlocks().get(pred).getInsns(); + return predInsns.get(predInsns.size()-1); + } + + /** + * Finds the corresponding move result for a given instruction + * + * @param insn {@code non-null;} an instruction that must always be + * followed by a move result + * @return {@code non-null;} the move result for the given instruction + */ + private SsaInsn getMoveForInsn(SsaInsn insn) { + int succ = insn.getBlock().getSuccessors().nextSetBit(0); + ArrayList succInsns = ssaMeth.getBlocks().get(succ).getInsns(); + return succInsns.get(0); + } + + /** + * Creates a link in the lattice between two EscapeSets due to a put + * instruction. The object being put is the child and the object being put + * into is the parent. A child set must always have an escape state at + * least as high as its parent. + * + * @param parentSet {@code non-null;} the EscapeSet for the object being put + * into + * @param childSet {@code non-null;} the EscapeSet for the object being put + */ + private void addEdge(EscapeSet parentSet, EscapeSet childSet) { + if (!childSet.parentSets.contains(parentSet)) { + childSet.parentSets.add(parentSet); + } + if (!parentSet.childSets.contains(childSet)) { + parentSet.childSets.add(childSet); + } + } + + /** + * Merges all links in the lattice among two EscapeSets. On return, the + * newNode will have its old links as well as all links from the oldNode. + * The oldNode has all its links removed. + * + * @param newNode {@code non-null;} the EscapeSet to merge all links into + * @param oldNode {@code non-null;} the EscapeSet to remove all links from + */ + private void replaceNode(EscapeSet newNode, EscapeSet oldNode) { + for (EscapeSet e : oldNode.parentSets) { + e.childSets.remove(oldNode); + e.childSets.add(newNode); + newNode.parentSets.add(e); + } + for (EscapeSet e : oldNode.childSets) { + e.parentSets.remove(oldNode); + e.parentSets.add(newNode); + newNode.childSets.add(e); + } + } + + /** + * Performs escape analysis on a method. Finds scalar replaceable arrays and + * replaces them with equivalent registers. + * + * @param ssaMethod {@code non-null;} method to process + */ + public static void process(SsaMethod ssaMethod) { + new EscapeAnalysis(ssaMethod).run(); + } + + /** + * Process a single instruction, looking for new objects resulting from + * move result or move param. + * + * @param insn {@code non-null;} instruction to process + */ + private void processInsn(SsaInsn insn) { + int op = insn.getOpcode().getOpcode(); + RegisterSpec result = insn.getResult(); + EscapeSet escSet; + + // Identify new objects + if (op == RegOps.MOVE_RESULT_PSEUDO && + result.getTypeBearer().getBasicType() == Type.BT_OBJECT) { + // Handle objects generated through move_result_pseudo + escSet = processMoveResultPseudoInsn(insn); + processRegister(result, escSet); + } else if (op == RegOps.MOVE_PARAM && + result.getTypeBearer().getBasicType() == Type.BT_OBJECT) { + // Track method arguments that are objects + escSet = new EscapeSet(result.getReg(), regCount, EscapeState.NONE); + latticeValues.add(escSet); + processRegister(result, escSet); + } else if (op == RegOps.MOVE_RESULT && + result.getTypeBearer().getBasicType() == Type.BT_OBJECT) { + // Track method return values that are objects + escSet = new EscapeSet(result.getReg(), regCount, EscapeState.NONE); + latticeValues.add(escSet); + processRegister(result, escSet); + } + } + + /** + * Determine the origin of a move result pseudo instruction that generates + * an object. Creates a new EscapeSet for the new object accordingly. + * + * @param insn {@code non-null;} move result pseudo instruction to process + * @return {@code non-null;} an EscapeSet for the object referred to by the + * move result pseudo instruction + */ + private EscapeSet processMoveResultPseudoInsn(SsaInsn insn) { + RegisterSpec result = insn.getResult(); + SsaInsn prevSsaInsn = getInsnForMove(insn); + int prevOpcode = prevSsaInsn.getOpcode().getOpcode(); + EscapeSet escSet; + RegisterSpec prevSource; + + switch(prevOpcode) { + // New instance / Constant + case RegOps.NEW_INSTANCE: + case RegOps.CONST: + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.NONE); + break; + // New array + case RegOps.NEW_ARRAY: + case RegOps.FILLED_NEW_ARRAY: + prevSource = prevSsaInsn.getSources().get(0); + if (prevSource.getTypeBearer().isConstant()) { + // New fixed array + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.NONE); + escSet.replaceableArray = true; + } else { + // New variable array + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.GLOBAL); + } + break; + // Loading a static object + case RegOps.GET_STATIC: + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.GLOBAL); + break; + // Type cast / load an object from a field or array + case RegOps.CHECK_CAST: + case RegOps.GET_FIELD: + case RegOps.AGET: + prevSource = prevSsaInsn.getSources().get(0); + int setIndex = findSetIndex(prevSource); + + // Set should already exist, try to find it + if (setIndex != latticeValues.size()) { + escSet = latticeValues.get(setIndex); + escSet.regSet.set(result.getReg()); + return escSet; + } + + // Set not found, must be either null or unknown + if (prevSource.getType() == Type.KNOWN_NULL) { + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.NONE); + } else { + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.GLOBAL); + } + break; + default: + return null; + } + + // Add the newly created escSet to the lattice and return it + latticeValues.add(escSet); + return escSet; + } + + /** + * Iterate through all the uses of a new object. + * + * @param result {@code non-null;} register where new object is stored + * @param escSet {@code non-null;} EscapeSet for the new object + */ + private void processRegister(RegisterSpec result, EscapeSet escSet) { + ArrayList regWorklist = new ArrayList(); + regWorklist.add(result); + + // Go through the worklist + while (!regWorklist.isEmpty()) { + int listSize = regWorklist.size() - 1; + RegisterSpec def = regWorklist.remove(listSize); + List useList = ssaMeth.getUseListForRegister(def.getReg()); + + // Handle all the uses of this register + for (SsaInsn use : useList) { + Rop useOpcode = use.getOpcode(); + + if (useOpcode == null) { + // Handle phis + processPhiUse(use, escSet, regWorklist); + } else { + // Handle other opcodes + processUse(def, use, escSet, regWorklist); + } + } + } + } + + /** + * Handles phi uses of new objects. Will merge together the sources of a phi + * into a single EscapeSet. Adds the result of the phi to the worklist so + * its uses can be followed. + * + * @param use {@code non-null;} phi use being processed + * @param escSet {@code non-null;} EscapeSet for the object + * @param regWorklist {@code non-null;} worklist of instructions left to + * process for this object + */ + private void processPhiUse(SsaInsn use, EscapeSet escSet, + ArrayList regWorklist) { + int setIndex = findSetIndex(use.getResult()); + if (setIndex != latticeValues.size()) { + // Check if result is in a set already + EscapeSet mergeSet = latticeValues.get(setIndex); + if (mergeSet != escSet) { + // If it is, merge the sets and states, then delete the copy + escSet.replaceableArray = false; + escSet.regSet.or(mergeSet.regSet); + if (escSet.escape.compareTo(mergeSet.escape) < 0) { + escSet.escape = mergeSet.escape; + } + replaceNode(escSet, mergeSet); + latticeValues.remove(setIndex); + } + } else { + // If no set is found, add it to this escSet and the worklist + escSet.regSet.set(use.getResult().getReg()); + regWorklist.add(use.getResult()); + } + } + + /** + * Handles non-phi uses of new objects. Checks to see how instruction is + * used and updates the escape state accordingly. + * + * @param def {@code non-null;} register holding definition of new object + * @param use {@code non-null;} use of object being processed + * @param escSet {@code non-null;} EscapeSet for the object + * @param regWorklist {@code non-null;} worklist of instructions left to + * process for this object + */ + private void processUse(RegisterSpec def, SsaInsn use, EscapeSet escSet, + ArrayList regWorklist) { + int useOpcode = use.getOpcode().getOpcode(); + switch (useOpcode) { + case RegOps.MOVE: + // Follow uses of the move by adding it to the worklist + escSet.regSet.set(use.getResult().getReg()); + regWorklist.add(use.getResult()); + break; + case RegOps.IF_EQ: + case RegOps.IF_NE: + case RegOps.CHECK_CAST: + // Compared objects can't be replaced, so promote if necessary + if (escSet.escape.compareTo(EscapeState.METHOD) < 0) { + escSet.escape = EscapeState.METHOD; + } + break; + case RegOps.APUT: + // For array puts, check for a constant array index + RegisterSpec putIndex = use.getSources().get(2); + if (!putIndex.getTypeBearer().isConstant()) { + // If not constant, array can't be replaced + escSet.replaceableArray = false; + } + // Intentional fallthrough + case RegOps.PUT_FIELD: + // Skip non-object puts + RegisterSpec putValue = use.getSources().get(0); + if (putValue.getTypeBearer().getBasicType() != Type.BT_OBJECT) { + break; + } + escSet.replaceableArray = false; + + // Raise 1st object's escape state to 2nd if 2nd is higher + RegisterSpecList sources = use.getSources(); + if (sources.get(0).getReg() == def.getReg()) { + int setIndex = findSetIndex(sources.get(1)); + if (setIndex != latticeValues.size()) { + EscapeSet parentSet = latticeValues.get(setIndex); + addEdge(parentSet, escSet); + if (escSet.escape.compareTo(parentSet.escape) < 0) { + escSet.escape = parentSet.escape; + } + } + } else { + int setIndex = findSetIndex(sources.get(0)); + if (setIndex != latticeValues.size()) { + EscapeSet childSet = latticeValues.get(setIndex); + addEdge(escSet, childSet); + if (childSet.escape.compareTo(escSet.escape) < 0) { + childSet.escape = escSet.escape; + } + } + } + break; + case RegOps.AGET: + // For array gets, check for a constant array index + RegisterSpec getIndex = use.getSources().get(1); + if (!getIndex.getTypeBearer().isConstant()) { + // If not constant, array can't be replaced + escSet.replaceableArray = false; + } + break; + case RegOps.PUT_STATIC: + // Static puts cause an object to escape globally + escSet.escape = EscapeState.GLOBAL; + break; + case RegOps.INVOKE_STATIC: + case RegOps.INVOKE_VIRTUAL: + case RegOps.INVOKE_SUPER: + case RegOps.INVOKE_DIRECT: + case RegOps.INVOKE_INTERFACE: + case RegOps.RETURN: + case RegOps.THROW: + // These operations cause an object to escape interprocedurally + escSet.escape = EscapeState.INTER; + break; + default: + break; + } + } + + /** + * Performs scalar replacement on all eligible arrays. + */ + private void scalarReplacement() { + // Iterate through lattice, looking for non-escaping replaceable arrays + for (EscapeSet escSet : latticeValues) { + if (!escSet.replaceableArray || escSet.escape != EscapeState.NONE) { + continue; + } + + // Get the instructions for the definition and move of the array + int e = escSet.regSet.nextSetBit(0); + SsaInsn def = ssaMeth.getDefinitionForRegister(e); + SsaInsn prev = getInsnForMove(def); + + // Create a map for the new registers that will be created + TypeBearer lengthReg = prev.getSources().get(0).getTypeBearer(); + int length = ((CstLiteralBits) lengthReg).getIntBits(); + ArrayList newRegs = + new ArrayList(length); + HashSet deletedInsns = new HashSet(); + + // Replace the definition of the array with registers + replaceDef(def, prev, length, newRegs); + + // Mark definition instructions for deletion + deletedInsns.add(prev); + deletedInsns.add(def); + + // Go through all uses of the array + List useList = ssaMeth.getUseListForRegister(e); + for (SsaInsn use : useList) { + // Replace the use with scalars and then mark it for deletion + replaceUse(use, prev, newRegs, deletedInsns); + deletedInsns.add(use); + } + + // Delete all marked instructions + ssaMeth.deleteInsns(deletedInsns); + ssaMeth.onInsnsChanged(); + + // Convert the method back to SSA form + SsaConverter.updateSsaMethod(ssaMeth, regCount); + + // Propagate and remove extra moves added by scalar replacement + movePropagate(); + } + } + + /** + * Replaces the instructions that define an array with equivalent registers. + * For each entry in the array, a register is created, initialized to zero. + * A mapping between this register and the corresponding array index is + * added. + * + * @param def {@code non-null;} move result instruction for array + * @param prev {@code non-null;} instruction for instantiating new array + * @param length size of the new array + * @param newRegs {@code non-null;} mapping of array indices to new + * registers to be populated + */ + private void replaceDef(SsaInsn def, SsaInsn prev, int length, + ArrayList newRegs) { + Type resultType = def.getResult().getType(); + + // Create new zeroed out registers for each element in the array + for (int i = 0; i < length; i++) { + Constant newZero = Zeroes.zeroFor(resultType.getComponentType()); + TypedConstant typedZero = (TypedConstant) newZero; + RegisterSpec newReg = + RegisterSpec.make(ssaMeth.makeNewSsaReg(), typedZero); + newRegs.add(newReg); + insertPlainInsnBefore(def, RegisterSpecList.EMPTY, newReg, + RegOps.CONST, newZero); + } + } + + /** + * Replaces the use for a scalar replaceable array. Gets and puts become + * move instructions, and array lengths and fills are handled. Can also + * identify ArrayIndexOutOfBounds exceptions and throw them if detected. + * + * @param use {@code non-null;} move result instruction for array + * @param prev {@code non-null;} instruction for instantiating new array + * @param newRegs {@code non-null;} mapping of array indices to new + * registers + * @param deletedInsns {@code non-null;} set of instructions marked for + * deletion + */ + private void replaceUse(SsaInsn use, SsaInsn prev, + ArrayList newRegs, + HashSet deletedInsns) { + int index; + int length = newRegs.size(); + SsaInsn next; + RegisterSpecList sources; + RegisterSpec source, result; + CstLiteralBits indexReg; + + switch (use.getOpcode().getOpcode()) { + case RegOps.AGET: + // Replace array gets with moves + next = getMoveForInsn(use); + sources = use.getSources(); + indexReg = ((CstLiteralBits) sources.get(1).getTypeBearer()); + index = indexReg.getIntBits(); + if (index < length) { + source = newRegs.get(index); + result = source.withReg(next.getResult().getReg()); + insertPlainInsnBefore(next, RegisterSpecList.make(source), + result, RegOps.MOVE, null); + } else { + // Throw an exception if the index is out of bounds + insertExceptionThrow(next, sources.get(1), deletedInsns); + deletedInsns.add(next.getBlock().getInsns().get(2)); + } + deletedInsns.add(next); + break; + case RegOps.APUT: + // Replace array puts with moves + sources = use.getSources(); + indexReg = ((CstLiteralBits) sources.get(2).getTypeBearer()); + index = indexReg.getIntBits(); + if (index < length) { + source = sources.get(0); + result = source.withReg(newRegs.get(index).getReg()); + insertPlainInsnBefore(use, RegisterSpecList.make(source), + result, RegOps.MOVE, null); + // Update the newReg entry to mark value as unknown now + newRegs.set(index, result.withSimpleType()); + } else { + // Throw an exception if the index is out of bounds + insertExceptionThrow(use, sources.get(2), deletedInsns); + } + break; + case RegOps.ARRAY_LENGTH: + // Replace array lengths with const instructions + TypeBearer lengthReg = prev.getSources().get(0).getTypeBearer(); + //CstInteger lengthReg = CstInteger.make(length); + next = getMoveForInsn(use); + insertPlainInsnBefore(next, RegisterSpecList.EMPTY, + next.getResult(), RegOps.CONST, + (Constant) lengthReg); + deletedInsns.add(next); + break; + case RegOps.MARK_LOCAL: + // Remove mark local instructions + break; + case RegOps.FILL_ARRAY_DATA: + // Create const instructions for each fill value + Insn ropUse = use.getOriginalRopInsn(); + FillArrayDataInsn fill = (FillArrayDataInsn) ropUse; + ArrayList constList = fill.getInitValues(); + for (int i = 0; i < length; i++) { + RegisterSpec newFill = + RegisterSpec.make(newRegs.get(i).getReg(), + (TypeBearer) constList.get(i)); + insertPlainInsnBefore(use, RegisterSpecList.EMPTY, newFill, + RegOps.CONST, constList.get(i)); + // Update the newRegs to hold the new const value + newRegs.set(i, newFill); + } + break; + default: + } + } + + /** + * Identifies extra moves added by scalar replacement and propagates the + * source of the move to any users of the result. + */ + private void movePropagate() { + for (int i = 0; i < ssaMeth.getRegCount(); i++) { + SsaInsn insn = ssaMeth.getDefinitionForRegister(i); + + // Look for move instructions only + if (insn == null || insn.getOpcode() == null || + insn.getOpcode().getOpcode() != RegOps.MOVE) { + continue; + } + + final ArrayList[] useList = ssaMeth.getUseListCopy(); + final RegisterSpec source = insn.getSources().get(0); + final RegisterSpec result = insn.getResult(); + + // Ignore moves that weren't added due to scalar replacement + if (source.getReg() < regCount && result.getReg() < regCount) { + continue; + } + + // Create a mapping from source to result + RegisterMapper mapper = new RegisterMapper() { + @Override + public int getNewRegisterCount() { + return ssaMeth.getRegCount(); + } + + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec.getReg() == result.getReg()) { + return source; + } + + return registerSpec; + } + }; + + // Modify all uses of the move to use the source of the move instead + for (SsaInsn use : useList[result.getReg()]) { + use.mapSourceRegisters(mapper); + } + } + } + + /** + * Runs escape analysis and scalar replacement of arrays. + */ + private void run() { + ssaMeth.forEachBlockDepthFirstDom(new SsaBasicBlock.Visitor() { + public void visitBlock (SsaBasicBlock block, + SsaBasicBlock unused) { + block.forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn(NormalSsaInsn insn) { + // do nothing + } + + public void visitPhiInsn(PhiInsn insn) { + // do nothing + } + + public void visitNonMoveInsn(NormalSsaInsn insn) { + processInsn(insn); + } + }); + } + }); + + // Go through lattice and promote fieldSets as necessary + for (EscapeSet e : latticeValues) { + if (e.escape != EscapeState.NONE) { + for (EscapeSet field : e.childSets) { + if (e.escape.compareTo(field.escape) > 0) { + field.escape = e.escape; + } + } + } + } + + // Perform scalar replacement for arrays + scalarReplacement(); + } + + /** + * Replaces instructions that trigger an ArrayIndexOutofBounds exception + * with an actual throw of the exception. + * + * @param insn {@code non-null;} instruction causing the exception + * @param index {@code non-null;} index value that is out of bounds + * @param deletedInsns {@code non-null;} set of instructions marked for + * deletion + */ + private void insertExceptionThrow(SsaInsn insn, RegisterSpec index, + HashSet deletedInsns) { + // Create a new ArrayIndexOutOfBoundsException + CstType exception = + new CstType(Exceptions.TYPE_ArrayIndexOutOfBoundsException); + insertThrowingInsnBefore(insn, RegisterSpecList.EMPTY, null, + RegOps.NEW_INSTANCE, exception); + + // Add a successor block with a move result pseudo for the exception + SsaBasicBlock currBlock = insn.getBlock(); + SsaBasicBlock newBlock = + currBlock.insertNewSuccessor(currBlock.getPrimarySuccessor()); + SsaInsn newInsn = newBlock.getInsns().get(0); + RegisterSpec newReg = + RegisterSpec.make(ssaMeth.makeNewSsaReg(), exception); + insertPlainInsnBefore(newInsn, RegisterSpecList.EMPTY, newReg, + RegOps.MOVE_RESULT_PSEUDO, null); + + // Add another successor block to initialize the exception + SsaBasicBlock newBlock2 = + newBlock.insertNewSuccessor(newBlock.getPrimarySuccessor()); + SsaInsn newInsn2 = newBlock2.getInsns().get(0); + CstNat newNat = new CstNat(new CstString(""), new CstString("(I)V")); + CstMethodRef newRef = new CstMethodRef(exception, newNat); + insertThrowingInsnBefore(newInsn2, RegisterSpecList.make(newReg, index), + null, RegOps.INVOKE_DIRECT, newRef); + deletedInsns.add(newInsn2); + + // Add another successor block to throw the new exception + SsaBasicBlock newBlock3 = + newBlock2.insertNewSuccessor(newBlock2.getPrimarySuccessor()); + SsaInsn newInsn3 = newBlock3.getInsns().get(0); + insertThrowingInsnBefore(newInsn3, RegisterSpecList.make(newReg), null, + RegOps.THROW, null); + newBlock3.replaceSuccessor(newBlock3.getPrimarySuccessorIndex(), + ssaMeth.getExitBlock().getIndex()); + deletedInsns.add(newInsn3); + } + + /** + * Inserts a new PlainInsn before the given instruction. + * TODO: move this somewhere more appropriate + * + * @param insn {@code non-null;} instruction to insert before + * @param newSources {@code non-null;} sources of new instruction + * @param newResult {@code non-null;} result of new instruction + * @param newOpcode opcode of new instruction + * @param cst {@code null-ok;} constant for new instruction, if any + */ + private void insertPlainInsnBefore(SsaInsn insn, + RegisterSpecList newSources, RegisterSpec newResult, int newOpcode, + Constant cst) { + + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop newRop; + if (newOpcode == RegOps.MOVE_RESULT_PSEUDO) { + newRop = Rops.opMoveResultPseudo(newResult.getType()); + } else { + newRop = Rops.ropFor(newOpcode, newResult, newSources, cst); + } + + Insn newRopInsn; + if (cst == null) { + newRopInsn = new PlainInsn(newRop, + originalRopInsn.getPosition(), newResult, newSources); + } else { + newRopInsn = new PlainCstInsn(newRop, + originalRopInsn.getPosition(), newResult, newSources, cst); + } + + NormalSsaInsn newInsn = new NormalSsaInsn(newRopInsn, insn.getBlock()); + List insns = insn.getBlock().getInsns(); + + insns.add(insns.lastIndexOf(insn), newInsn); + ssaMeth.onInsnAdded(newInsn); + } + + /** + * Inserts a new ThrowingInsn before the given instruction. + * TODO: move this somewhere more appropriate + * + * @param insn {@code non-null;} instruction to insert before + * @param newSources {@code non-null;} sources of new instruction + * @param newResult {@code non-null;} result of new instruction + * @param newOpcode opcode of new instruction + * @param cst {@code null-ok;} constant for new instruction, if any + */ + private void insertThrowingInsnBefore(SsaInsn insn, + RegisterSpecList newSources, RegisterSpec newResult, int newOpcode, + Constant cst) { + + Insn origRopInsn = insn.getOriginalRopInsn(); + Rop newRop = Rops.ropFor(newOpcode, newResult, newSources, cst); + Insn newRopInsn; + if (cst == null) { + newRopInsn = new ThrowingInsn(newRop, + origRopInsn.getPosition(), newSources, StdTypeList.EMPTY); + } else { + newRopInsn = new ThrowingCstInsn(newRop, + origRopInsn.getPosition(), newSources, StdTypeList.EMPTY, cst); + } + + NormalSsaInsn newInsn = new NormalSsaInsn(newRopInsn, insn.getBlock()); + List insns = insn.getBlock().getInsns(); + + insns.add(insns.lastIndexOf(insn), newInsn); + ssaMeth.onInsnAdded(newInsn); + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/InterferenceRegisterMapper.java b/dexlib/src/main/java/com/android/dx/ssa/InterferenceRegisterMapper.java new file mode 100644 index 000000000..446888dca --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/InterferenceRegisterMapper.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.ssa.back.InterferenceGraph; +import com.android.dx.util.BitIntSet; +import com.android.dx.util.IntSet; +import java.util.ArrayList; + +/** + * A register mapper that keeps track of the accumulated interference + * information for the registers in the new namespace. + * + * Please note that this mapper requires that the old namespace does not + * have variable register widths/categories, and the new namespace does. + */ +public class InterferenceRegisterMapper extends BasicRegisterMapper { + /** + * Array of interference sets. ArrayList is indexed by new namespace + * and BitIntSet's are indexed by old namespace. The list expands + * as needed and missing items are assumed to interfere with nothing. + * + * Bit sets are always used here, unlike elsewhere, because the max + * size of this matrix will be (countSsaRegs * countRopRegs), which may + * grow to hundreds of K but not megabytes. + */ + private final ArrayList newRegInterference; + + /** the interference graph for the old namespace */ + private final InterferenceGraph oldRegInterference; + + /** + * Constructs an instance + * + * @param countOldRegisters number of registers in old namespace + */ + public InterferenceRegisterMapper(InterferenceGraph oldRegInterference, + int countOldRegisters) { + super(countOldRegisters); + + newRegInterference = new ArrayList(); + this.oldRegInterference = oldRegInterference; + } + + /** {@inheritDoc} */ + @Override + public void addMapping(int oldReg, int newReg, int category) { + super.addMapping(oldReg, newReg, category); + + addInterfence(newReg, oldReg); + + if (category == 2) { + addInterfence(newReg + 1, oldReg); + } + } + + /** + * Checks to see if old namespace reg {@code oldReg} interferes + * with what currently maps to {@code newReg}. + * + * @param oldReg old namespace register + * @param newReg new namespace register + * @param category category of old namespace register + * @return true if oldReg will interfere with newReg + */ + public boolean interferes(int oldReg, int newReg, int category) { + if (newReg >= newRegInterference.size()) { + return false; + } else { + IntSet existing = newRegInterference.get(newReg); + + if (existing == null) { + return false; + } else if (category == 1) { + return existing.has(oldReg); + } else { + return existing.has(oldReg) + || (interferes(oldReg, newReg+1, category-1)); + } + } + } + + /** + * Checks to see if old namespace reg {@code oldReg} interferes + * with what currently maps to {@code newReg}. + * + * @param oldSpec {@code non-null;} old namespace register + * @param newReg new namespace register + * @return true if oldReg will interfere with newReg + */ + public boolean interferes(RegisterSpec oldSpec, int newReg) { + return interferes(oldSpec.getReg(), newReg, oldSpec.getCategory()); + } + + /** + * Adds a register's interference set to the interference list, + * growing it if necessary. + * + * @param newReg register in new namespace + * @param oldReg register in old namespace + */ + private void addInterfence(int newReg, int oldReg) { + newRegInterference.ensureCapacity(newReg + 1); + + while (newReg >= newRegInterference.size()) { + newRegInterference.add(new BitIntSet(newReg +1)); + } + + oldRegInterference.mergeInterferenceSet( + oldReg, newRegInterference.get(newReg)); + } + + /** + * Checks to see if any of a set of old-namespace registers are + * pinned to the specified new-namespace reg + category. Takes into + * account the category of the old-namespace registers. + * + * @param oldSpecs {@code non-null;} set of old-namespace regs + * @param newReg {@code >= 0;} new-namespace register + * @param targetCategory {@code 1..2;} the number of adjacent new-namespace + * registers (starting at ropReg) to consider + * @return true if any of the old-namespace register have been mapped + * to the new-namespace register + category + */ + public boolean areAnyPinned(RegisterSpecList oldSpecs, + int newReg, int targetCategory) { + int sz = oldSpecs.size(); + + for (int i = 0; i < sz; i++) { + RegisterSpec oldSpec = oldSpecs.get(i); + int r = oldToNew(oldSpec.getReg()); + + /* + * If oldSpec is a category-2 register, then check both newReg + * and newReg - 1. + */ + if (r == newReg + || (oldSpec.getCategory() == 2 && (r + 1) == newReg) + || (targetCategory == 2 && (r == newReg + 1))) { + return true; + } + } + + return false; + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/LiteralOpUpgrader.java b/dexlib/src/main/java/com/android/dx/ssa/LiteralOpUpgrader.java new file mode 100644 index 000000000..ad017e23c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/LiteralOpUpgrader.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.PlainCstInsn; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.TranslationAdvice; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstLiteralBits; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import java.util.ArrayList; +import java.util.List; + +/** + * Upgrades insn to their literal (constant-immediate) equivalent if possible. + * Also switches IF instructions that compare with a constant zero or null + * to be their IF_*Z equivalents. + */ +public class LiteralOpUpgrader { + + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** + * Process a method. + * + * @param ssaMethod {@code non-null;} method to process + */ + public static void process(SsaMethod ssaMethod) { + LiteralOpUpgrader dc; + + dc = new LiteralOpUpgrader(ssaMethod); + + dc.run(); + } + + private LiteralOpUpgrader(SsaMethod ssaMethod) { + this.ssaMeth = ssaMethod; + } + + /** + * Returns true if the register contains an integer 0 or a known-null + * object reference + * + * @param spec non-null spec + * @return true for 0 or null type bearers + */ + private static boolean isConstIntZeroOrKnownNull(RegisterSpec spec) { + TypeBearer tb = spec.getTypeBearer(); + if (tb instanceof CstLiteralBits) { + CstLiteralBits clb = (CstLiteralBits) tb; + return (clb.getLongBits() == 0); + } + return false; + } + + /** + * Run the literal op upgrader + */ + private void run() { + final TranslationAdvice advice = Optimizer.getAdvice(); + + ssaMeth.forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn(NormalSsaInsn insn) { + // do nothing + } + + public void visitPhiInsn(PhiInsn insn) { + // do nothing + } + + public void visitNonMoveInsn(NormalSsaInsn insn) { + + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop opcode = originalRopInsn.getOpcode(); + RegisterSpecList sources = insn.getSources(); + + // Replace insns with constant results with const insns + if (tryReplacingWithConstant(insn)) return; + + if (sources.size() != 2 ) { + // We're only dealing with two-source insns here. + return; + } + + if (opcode.getBranchingness() == Rop.BRANCH_IF) { + /* + * An if instruction can become an if-*z instruction. + */ + if (isConstIntZeroOrKnownNull(sources.get(0))) { + replacePlainInsn(insn, sources.withoutFirst(), + RegOps.flippedIfOpcode(opcode.getOpcode()), null); + } else if (isConstIntZeroOrKnownNull(sources.get(1))) { + replacePlainInsn(insn, sources.withoutLast(), + opcode.getOpcode(), null); + } + } else if (advice.hasConstantOperation( + opcode, sources.get(0), sources.get(1))) { + insn.upgradeToLiteral(); + } else if (opcode.isCommutative() + && advice.hasConstantOperation( + opcode, sources.get(1), sources.get(0))) { + /* + * An instruction can be commuted to a literal operation + */ + + insn.setNewSources( + RegisterSpecList.make( + sources.get(1), sources.get(0))); + + insn.upgradeToLiteral(); + } + } + }); + } + + /** + * Tries to replace an instruction with a const instruction. The given + * instruction must have a constant result for it to be replaced. + * + * @param insn {@code non-null;} instruction to try to replace + * @return true if the instruction was replaced + */ + private boolean tryReplacingWithConstant(NormalSsaInsn insn) { + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop opcode = originalRopInsn.getOpcode(); + RegisterSpec result = insn.getResult(); + + if (result != null && !ssaMeth.isRegALocal(result) && + opcode.getOpcode() != RegOps.CONST) { + TypeBearer type = insn.getResult().getTypeBearer(); + if (type.isConstant() && type.getBasicType() == Type.BT_INT) { + // Replace the instruction with a constant + replacePlainInsn(insn, RegisterSpecList.EMPTY, + RegOps.CONST, (Constant) type); + + // Remove the source as well if this is a move-result-pseudo + if (opcode.getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + int pred = insn.getBlock().getPredecessors().nextSetBit(0); + ArrayList predInsns = + ssaMeth.getBlocks().get(pred).getInsns(); + NormalSsaInsn sourceInsn = + (NormalSsaInsn) predInsns.get(predInsns.size()-1); + replacePlainInsn(sourceInsn, RegisterSpecList.EMPTY, + RegOps.GOTO, null); + } + return true; + } + } + return false; + } + + /** + * Replaces an SsaInsn containing a PlainInsn with a new PlainInsn. The + * new PlainInsn is constructed with a new RegOp and new sources. + * + * TODO move this somewhere else. + * + * @param insn {@code non-null;} an SsaInsn containing a PlainInsn + * @param newSources {@code non-null;} new sources list for new insn + * @param newOpcode A RegOp from {@link RegOps} + * @param cst {@code null-ok;} constant for new instruction, if any + */ + private void replacePlainInsn(NormalSsaInsn insn, + RegisterSpecList newSources, int newOpcode, Constant cst) { + + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop newRop = Rops.ropFor(newOpcode, insn.getResult(), newSources, cst); + Insn newRopInsn; + if (cst == null) { + newRopInsn = new PlainInsn(newRop, originalRopInsn.getPosition(), + insn.getResult(), newSources); + } else { + newRopInsn = new PlainCstInsn(newRop, originalRopInsn.getPosition(), + insn.getResult(), newSources, cst); + } + NormalSsaInsn newInsn = new NormalSsaInsn(newRopInsn, insn.getBlock()); + + List insns = insn.getBlock().getInsns(); + + ssaMeth.onInsnRemoved(insn); + insns.set(insns.lastIndexOf(insn), newInsn); + ssaMeth.onInsnAdded(newInsn); + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/LocalVariableExtractor.java b/dexlib/src/main/java/com/android/dx/ssa/LocalVariableExtractor.java new file mode 100644 index 000000000..b86bed67f --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/LocalVariableExtractor.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecSet; +import com.android.dx.util.IntList; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +/** + * Code to figure out which local variables are active at which points in + * a method. Stolen and retrofitted from + * com.android.dx.rop.code.LocalVariableExtractor + * + * TODO remove this. Allow Rop-form LocalVariableInfo to be passed in, + * converted, and adapted through edge-splitting. + */ +public class LocalVariableExtractor { + /** {@code non-null;} method being extracted from */ + private final SsaMethod method; + + /** {@code non-null;} block list for the method */ + private final ArrayList blocks; + + /** {@code non-null;} result in-progress */ + private final LocalVariableInfo resultInfo; + + /** {@code non-null;} work set indicating blocks needing to be processed */ + private final BitSet workSet; + + /** + * Extracts out all the local variable information from the given method. + * + * @param method {@code non-null;} the method to extract from + * @return {@code non-null;} the extracted information + */ + public static LocalVariableInfo extract(SsaMethod method) { + LocalVariableExtractor lve = new LocalVariableExtractor(method); + return lve.doit(); + } + + /** + * Constructs an instance. This method is private. Use {@link #extract}. + * + * @param method {@code non-null;} the method to extract from + */ + private LocalVariableExtractor(SsaMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + ArrayList blocks = method.getBlocks(); + + this.method = method; + this.blocks = blocks; + this.resultInfo = new LocalVariableInfo(method); + this.workSet = new BitSet(blocks.size()); + } + + /** + * Does the extraction. + * + * @return {@code non-null;} the extracted information + */ + private LocalVariableInfo doit() { + + //FIXME why is this needed here? + if (method.getRegCount() > 0 ) { + for (int bi = method.getEntryBlockIndex(); + bi >= 0; + bi = workSet.nextSetBit(0)) { + workSet.clear(bi); + processBlock(bi); + } + } + + resultInfo.setImmutable(); + return resultInfo; + } + + /** + * Processes a single block. + * + * @param blockIndex {@code >= 0;} block index of the block to process + */ + private void processBlock(int blockIndex) { + RegisterSpecSet primaryState + = resultInfo.mutableCopyOfStarts(blockIndex); + SsaBasicBlock block = blocks.get(blockIndex); + List insns = block.getInsns(); + int insnSz = insns.size(); + + // The exit block has no insns and no successors + if (blockIndex == method.getExitBlockIndex()) { + return; + } + + /* + * We may have to treat the last instruction specially: If it + * can (but doesn't always) throw, and the exception can be + * caught within the same method, then we need to use the + * state *before* executing it to be what is merged into + * exception targets. + */ + SsaInsn lastInsn = insns.get(insnSz - 1); + boolean hasExceptionHandlers + = lastInsn.getOriginalRopInsn().getCatches().size() !=0 ; + boolean canThrowDuringLastInsn = hasExceptionHandlers + && (lastInsn.getResult() != null); + int freezeSecondaryStateAt = insnSz - 1; + RegisterSpecSet secondaryState = primaryState; + + /* + * Iterate over the instructions, adding information for each place + * that the active variable set changes. + */ + + for (int i = 0; i < insnSz; i++) { + if (canThrowDuringLastInsn && (i == freezeSecondaryStateAt)) { + // Until this point, primaryState == secondaryState. + primaryState.setImmutable(); + primaryState = primaryState.mutableCopy(); + } + + SsaInsn insn = insns.get(i); + RegisterSpec result; + + result = insn.getLocalAssignment(); + + if (result == null) { + // We may be nuking an existing local + + result = insn.getResult(); + + if (result != null && primaryState.get(result.getReg()) != null) { + primaryState.remove(primaryState.get(result.getReg())); + } + continue; + } + + result = result.withSimpleType(); + + RegisterSpec already = primaryState.get(result); + /* + * The equals() check ensures we only add new info if + * the instruction causes a change to the set of + * active variables. + */ + if (!result.equals(already)) { + /* + * If this insn represents a local moving from one register + * to another, remove the association between the old register + * and the local. + */ + RegisterSpec previous + = primaryState.localItemToSpec(result.getLocalItem()); + + if (previous != null + && (previous.getReg() != result.getReg())) { + + primaryState.remove(previous); + } + + resultInfo.addAssignment(insn, result); + primaryState.put(result); + } + } + + primaryState.setImmutable(); + + /* + * Merge this state into the start state for each successor, + * and update the work set where required (that is, in cases + * where the start state for a block changes). + */ + + IntList successors = block.getSuccessorList(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessorIndex(); + + for (int i = 0; i < succSz; i++) { + int succ = successors.get(i); + RegisterSpecSet state = (succ == primarySuccessor) ? + primaryState : secondaryState; + + if (resultInfo.mergeStarts(succ, state)) { + workSet.set(succ); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/LocalVariableInfo.java b/dexlib/src/main/java/com/android/dx/ssa/LocalVariableInfo.java new file mode 100644 index 000000000..152053187 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/LocalVariableInfo.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecSet; +import com.android.dx.util.MutabilityControl; +import java.util.HashMap; +import java.util.List; + +/** + * Container for local variable information for a particular {@link + * com.android.dx.ssa.SsaMethod}. + * Stolen from {@link com.android.dx.rop.code.LocalVariableInfo}. + */ +public class LocalVariableInfo extends MutabilityControl { + /** {@code >= 0;} the register count for the method */ + private final int regCount; + + /** + * {@code non-null;} {@link com.android.dx.rop.code.RegisterSpecSet} to use when indicating a block + * that has no locals; it is empty and immutable but has an appropriate + * max size for the method + */ + private final RegisterSpecSet emptySet; + + /** + * {@code non-null;} array consisting of register sets representing the + * sets of variables already assigned upon entry to each block, + * where array indices correspond to block indices + */ + private final RegisterSpecSet[] blockStarts; + + /** {@code non-null;} map from instructions to the variable each assigns */ + private final HashMap insnAssignments; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method being represented by this instance + */ + public LocalVariableInfo(SsaMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + List blocks = method.getBlocks(); + + this.regCount = method.getRegCount(); + this.emptySet = new RegisterSpecSet(regCount); + this.blockStarts = new RegisterSpecSet[blocks.size()]; + this.insnAssignments = + new HashMap(/*hint here*/); + + emptySet.setImmutable(); + } + + /** + * Sets the register set associated with the start of the block with + * the given index. + * + * @param index {@code >= 0;} the block index + * @param specs {@code non-null;} the register set to associate with the block + */ + public void setStarts(int index, RegisterSpecSet specs) { + throwIfImmutable(); + + if (specs == null) { + throw new NullPointerException("specs == null"); + } + + try { + blockStarts[index] = specs; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus index"); + } + } + + /** + * Merges the given register set into the set for the block with the + * given index. If there was not already an associated set, then this + * is the same as calling {@link #setStarts}. Otherwise, this will + * merge the two sets and call {@link #setStarts} on the result of the + * merge. + * + * @param index {@code >= 0;} the block index + * @param specs {@code non-null;} the register set to merge into the start set + * for the block + * @return {@code true} if the merge resulted in an actual change + * to the associated set (including storing one for the first time) or + * {@code false} if there was no change + */ + public boolean mergeStarts(int index, RegisterSpecSet specs) { + RegisterSpecSet start = getStarts0(index); + boolean changed = false; + + if (start == null) { + setStarts(index, specs); + return true; + } + + RegisterSpecSet newStart = start.mutableCopy(); + newStart.intersect(specs, true); + + if (start.equals(newStart)) { + return false; + } + + newStart.setImmutable(); + setStarts(index, newStart); + + return true; + } + + /** + * Gets the register set associated with the start of the block + * with the given index. This returns an empty set with the appropriate + * max size if no set was associated with the block in question. + * + * @param index {@code >= 0;} the block index + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(int index) { + RegisterSpecSet result = getStarts0(index); + + return (result != null) ? result : emptySet; + } + + /** + * Gets the register set associated with the start of the given + * block. This is just convenient shorthand for + * {@code getStarts(block.getLabel())}. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(SsaBasicBlock block) { + return getStarts(block.getIndex()); + } + + /** + * Gets a mutable copy of the register set associated with the + * start of the block with the given index. This returns a + * newly-allocated empty {@link RegisterSpecSet} of appropriate + * max size if there is not yet any set associated with the block. + * + * @param index {@code >= 0;} the block index + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet mutableCopyOfStarts(int index) { + RegisterSpecSet result = getStarts0(index); + + return (result != null) ? + result.mutableCopy() : new RegisterSpecSet(regCount); + } + + /** + * Adds an assignment association for the given instruction and + * register spec. This throws an exception if the instruction + * doesn't actually perform a named variable assignment. + * + * Note: Although the instruction contains its own spec for + * the result, it still needs to be passed in explicitly to this + * method, since the spec that is stored here should always have a + * simple type and the one in the instruction can be an arbitrary + * {@link com.android.dx.rop.type.TypeBearer} (such as a constant value). + * + * @param insn {@code non-null;} the instruction in question + * @param spec {@code non-null;} the associated register spec + */ + public void addAssignment(SsaInsn insn, RegisterSpec spec) { + throwIfImmutable(); + + if (insn == null) { + throw new NullPointerException("insn == null"); + } + + if (spec == null) { + throw new NullPointerException("spec == null"); + } + + insnAssignments.put(insn, spec); + } + + /** + * Gets the named register being assigned by the given instruction, if + * previously stored in this instance. + * + * @param insn {@code non-null;} instruction in question + * @return {@code null-ok;} the named register being assigned, if any + */ + public RegisterSpec getAssignment(SsaInsn insn) { + return insnAssignments.get(insn); + } + + /** + * Gets the number of assignments recorded by this instance. + * + * @return {@code >= 0;} the number of assignments + */ + public int getAssignmentCount() { + return insnAssignments.size(); + } + + public void debugDump() { + for (int index = 0 ; index < blockStarts.length; index++) { + if (blockStarts[index] == null) { + continue; + } + + if (blockStarts[index] == emptySet) { + System.out.printf("%04x: empty set\n", index); + } else { + System.out.printf("%04x: %s\n", index, blockStarts[index]); + } + } + } + + /** + * Helper method, to get the starts for a index, throwing the + * right exception for range problems. + * + * @param index {@code >= 0;} the block index + * @return {@code null-ok;} associated register set or {@code null} if there + * is none + */ + private RegisterSpecSet getStarts0(int index) { + try { + return blockStarts[index]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus index"); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/MoveParamCombiner.java b/dexlib/src/main/java/com/android/dx/ssa/MoveParamCombiner.java new file mode 100644 index 000000000..567644728 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/MoveParamCombiner.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.CstInsn; +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.cst.CstInteger; +import java.util.HashSet; +import java.util.List; + +/** + * Combine identical move-param insns, which may result from Ropper's + * handling of synchronized methods. + */ +public class MoveParamCombiner { + + /** method to process */ + private final SsaMethod ssaMeth; + + /** + * Processes a method with this optimization step. + * + * @param ssaMethod method to process + */ + public static void process(SsaMethod ssaMethod) { + new MoveParamCombiner(ssaMethod).run(); + } + + private MoveParamCombiner(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + } + + /** + * Runs this optimization step. + */ + private void run() { + // This will contain the definition specs for each parameter + final RegisterSpec[] paramSpecs + = new RegisterSpec[ssaMeth.getParamWidth()]; + + // Insns to delete when all done + final HashSet deletedInsns = new HashSet(); + + ssaMeth.forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn (NormalSsaInsn insn) { + } + public void visitPhiInsn (PhiInsn phi) { + } + public void visitNonMoveInsn (NormalSsaInsn insn) { + if (insn.getOpcode().getOpcode() != RegOps.MOVE_PARAM) { + return; + } + + int param = getParamIndex(insn); + + if (paramSpecs[param] == null) { + paramSpecs[param] = insn.getResult(); + } else { + final RegisterSpec specA = paramSpecs[param]; + final RegisterSpec specB = insn.getResult(); + LocalItem localA = specA.getLocalItem(); + LocalItem localB = specB.getLocalItem(); + LocalItem newLocal; + + /* + * Is there local information to preserve? + */ + + if (localA == null) { + newLocal = localB; + } else if (localB == null) { + newLocal = localA; + } else if (localA.equals(localB)) { + newLocal = localA; + } else { + /* + * Oddly, these two identical move-params have distinct + * debug info. We'll just keep them distinct. + */ + return; + } + + ssaMeth.getDefinitionForRegister(specA.getReg()) + .setResultLocal(newLocal); + + /* + * Map all uses of specB to specA + */ + + RegisterMapper mapper = new RegisterMapper() { + /** {@inheritDoc} */ + public int getNewRegisterCount() { + return ssaMeth.getRegCount(); + } + + /** {@inheritDoc} */ + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec.getReg() == specB.getReg()) { + return specA; + } + + return registerSpec; + } + }; + + List uses + = ssaMeth.getUseListForRegister(specB.getReg()); + + // Use list is modified by mapSourceRegisters + for (int i = uses.size() - 1; i >= 0; i--) { + SsaInsn use = uses.get(i); + use.mapSourceRegisters(mapper); + } + + deletedInsns.add(insn); + } + + } + }); + + ssaMeth.deleteInsns(deletedInsns); + } + + /** + * Returns the parameter index associated with a move-param insn. Does + * not verify that the insn is a move-param insn. + * + * @param insn {@code non-null;} a move-param insn + * @return {@code >=0;} parameter index + */ + private int getParamIndex(NormalSsaInsn insn) { + CstInsn cstInsn = (CstInsn)(insn.getOriginalRopInsn()); + + int param = ((CstInteger)cstInsn.getConstant()).getValue(); + return param; + } + +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/NormalSsaInsn.java b/dexlib/src/main/java/com/android/dx/ssa/NormalSsaInsn.java new file mode 100644 index 000000000..61c12e54c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/NormalSsaInsn.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; + +/** + * A "normal" (non-phi) instruction in SSA form. Always wraps a rop insn. + */ +public final class NormalSsaInsn extends SsaInsn implements Cloneable { + /** {@code non-null;} rop insn that we're wrapping */ + private Insn insn; + + /** + * Creates an instance. + * + * @param insn Rop insn to wrap + * @param block block that contains this insn + */ + NormalSsaInsn(final Insn insn, final SsaBasicBlock block) { + super(insn.getResult(), block); + this.insn = insn; + } + + /** {@inheritDoc} */ + @Override + public final void mapSourceRegisters(RegisterMapper mapper) { + RegisterSpecList oldSources = insn.getSources(); + RegisterSpecList newSources = mapper.map(oldSources); + + if (newSources != oldSources) { + insn = insn.withNewRegisters(getResult(), newSources); + getBlock().getParent().onSourcesChanged(this, oldSources); + } + } + + /** + * Changes one of the insn's sources. New source should be of same type + * and category. + * + * @param index {@code >=0;} index of source to change + * @param newSpec spec for new source + */ + public final void changeOneSource(int index, RegisterSpec newSpec) { + RegisterSpecList origSources = insn.getSources(); + int sz = origSources.size(); + RegisterSpecList newSources = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + newSources.set(i, i == index ? newSpec : origSources.get(i)); + } + + newSources.setImmutable(); + + RegisterSpec origSpec = origSources.get(index); + if (origSpec.getReg() != newSpec.getReg()) { + /* + * If the register remains unchanged, we're only changing + * the type or local var name so don't update use list + */ + getBlock().getParent().onSourceChanged(this, origSpec, newSpec); + } + + insn = insn.withNewRegisters(getResult(), newSources); + } + + /** + * Changes the source list of the insn. New source list should be the + * same size and consist of sources of identical types. + * + * @param newSources non-null new sources list. + */ + public final void setNewSources (RegisterSpecList newSources) { + RegisterSpecList origSources = insn.getSources(); + + if (origSources.size() != newSources.size()) { + throw new RuntimeException("Sources counts don't match"); + } + + insn = insn.withNewRegisters(getResult(), newSources); + } + + /** {@inheritDoc} */ + @Override + public NormalSsaInsn clone() { + return (NormalSsaInsn) super.clone(); + } + + /** + * Like rop.Insn.getSources(). + * + * @return {@code null-ok;} sources list + */ + @Override + public RegisterSpecList getSources() { + return insn.getSources(); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toRopInsn().toHuman(); + } + + /** {@inheritDoc} */ + @Override + public Insn toRopInsn() { + return insn.withNewRegisters(getResult(), insn.getSources()); + } + + /** + * @return the Rop opcode for this insn + */ + @Override + public Rop getOpcode() { + return insn.getOpcode(); + } + + /** {@inheritDoc} */ + @Override + public Insn getOriginalRopInsn() { + return insn; + } + + /** {@inheritDoc} */ + @Override + public RegisterSpec getLocalAssignment() { + RegisterSpec assignment; + + if (insn.getOpcode().getOpcode() == RegOps.MARK_LOCAL) { + assignment = insn.getSources().get(0); + } else { + assignment = getResult(); + } + + if (assignment == null) { + return null; + } + + LocalItem local = assignment.getLocalItem(); + + if (local == null) { + return null; + } + + return assignment; + } + + /** + * Upgrades this insn to a version that represents the constant source + * literally. If the upgrade is not possible, this does nothing. + * + * @see Insn#withSourceLiteral + */ + public void upgradeToLiteral() { + RegisterSpecList oldSources = insn.getSources(); + + insn = insn.withSourceLiteral(); + getBlock().getParent().onSourcesChanged(this, oldSources); + } + + /** + * @return true if this is a move (but not a move-operand) instruction + */ + @Override + public boolean isNormalMoveInsn() { + return insn.getOpcode().getOpcode() == RegOps.MOVE; + } + + /** {@inheritDoc} */ + @Override + public boolean isMoveException() { + return insn.getOpcode().getOpcode() == RegOps.MOVE_EXCEPTION; + } + + /** {@inheritDoc} */ + @Override + public boolean canThrow() { + return insn.canThrow(); + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor v) { + if (isNormalMoveInsn()) { + v.visitMoveInsn(this); + } else { + v.visitNonMoveInsn(this); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isPhiOrMove() { + return isNormalMoveInsn(); + } + + /** + * {@inheritDoc} + * + * TODO: Increase the scope of this. + */ + @Override + public boolean hasSideEffect() { + Rop opcode = getOpcode(); + + if (opcode.getBranchingness() != Rop.BRANCH_NONE) { + return true; + } + + boolean hasLocalSideEffect + = Optimizer.getPreserveLocals() && getLocalAssignment() != null; + + switch (opcode.getOpcode()) { + case RegOps.MOVE_RESULT: + case RegOps.MOVE: + case RegOps.CONST: + return hasLocalSideEffect; + default: + return true; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/Optimizer.java b/dexlib/src/main/java/com/android/dx/ssa/Optimizer.java new file mode 100644 index 000000000..8c2e284c2 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/Optimizer.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.TranslationAdvice; +import com.android.dx.ssa.back.LivenessAnalyzer; +import com.android.dx.ssa.back.SsaToRop; +import java.util.EnumSet; + +/** + * Runs a method through the SSA form conversion, any optimization algorithms, + * and returns it to rop form. + */ +public class Optimizer { + private static boolean preserveLocals = true; + + private static TranslationAdvice advice; + + /** optional optimizer steps */ + public enum OptionalStep { + MOVE_PARAM_COMBINER, SCCP, LITERAL_UPGRADE, CONST_COLLECTOR, + ESCAPE_ANALYSIS + } + + /** + * @return true if local variable information should be preserved, even + * at code size/register size cost + */ + public static boolean getPreserveLocals() { + return preserveLocals; + } + + /** + * @return {@code non-null;} translation advice + */ + public static TranslationAdvice getAdvice() { + return advice; + } + + /** + * Runs optimization algorthims over this method, and returns a new + * instance of RopMethod with the changes. + * + * @param rmeth method to process + * @param paramWidth the total width, in register-units, of this method's + * parameters + * @param isStatic true if this method has no 'this' pointer argument. + * @param inPreserveLocals true if local variable info should be preserved, + * at the cost of some registers and insns + * @param inAdvice {@code non-null;} translation advice + * @return optimized method + */ + public static RopMethod optimize(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + return optimize(rmeth, paramWidth, isStatic, inPreserveLocals, inAdvice, + EnumSet.allOf(OptionalStep.class)); + } + + /** + * Runs optimization algorthims over this method, and returns a new + * instance of RopMethod with the changes. + * + * @param rmeth method to process + * @param paramWidth the total width, in register-units, of this method's + * parameters + * @param isStatic true if this method has no 'this' pointer argument. + * @param inPreserveLocals true if local variable info should be preserved, + * at the cost of some registers and insns + * @param inAdvice {@code non-null;} translation advice + * @param steps set of optional optimization steps to run + * @return optimized method + */ + public static RopMethod optimize(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice, EnumSet steps) { + SsaMethod ssaMeth = null; + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + runSsaFormSteps(ssaMeth, steps); + + RopMethod resultMeth = SsaToRop.convertToRopMethod(ssaMeth, false); + + if (resultMeth.getBlocks().getRegCount() + > advice.getMaxOptimalRegisterCount()) { + // Try to see if we can squeeze it under the register count bar + resultMeth = optimizeMinimizeRegisters(rmeth, paramWidth, isStatic, + steps); + } + return resultMeth; + } + + /** + * Runs the optimizer with a strategy to minimize the number of rop-form + * registers used by the end result. Dex bytecode does not have instruction + * forms that take register numbers larger than 15 for all instructions. + * If we've produced a method that uses more than 16 registers, try again + * with a different strategy to see if we can get under the bar. The end + * result will be much more efficient. + * + * @param rmeth method to process + * @param paramWidth the total width, in register-units, of this method's + * parameters + * @param isStatic true if this method has no 'this' pointer argument. + * @param steps set of optional optimization steps to run + * @return optimized method + */ + private static RopMethod optimizeMinimizeRegisters(RopMethod rmeth, + int paramWidth, boolean isStatic, + EnumSet steps) { + SsaMethod ssaMeth; + RopMethod resultMeth; + + ssaMeth = SsaConverter.convertToSsaMethod( + rmeth, paramWidth, isStatic); + + EnumSet newSteps = steps.clone(); + + /* + * CONST_COLLECTOR trades insns for registers, which is not an + * appropriate strategy here. + */ + newSteps.remove(OptionalStep.CONST_COLLECTOR); + + runSsaFormSteps(ssaMeth, newSteps); + + resultMeth = SsaToRop.convertToRopMethod(ssaMeth, true); + return resultMeth; + } + + private static void runSsaFormSteps(SsaMethod ssaMeth, + EnumSet steps) { + boolean needsDeadCodeRemover = true; + + if (steps.contains(OptionalStep.MOVE_PARAM_COMBINER)) { + MoveParamCombiner.process(ssaMeth); + } + + if (steps.contains(OptionalStep.SCCP)) { + SCCP.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + if (steps.contains(OptionalStep.LITERAL_UPGRADE)) { + LiteralOpUpgrader.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + /* + * ESCAPE_ANALYSIS impacts debuggability, so left off by default + */ + steps.remove(OptionalStep.ESCAPE_ANALYSIS); + if (steps.contains(OptionalStep.ESCAPE_ANALYSIS)) { + EscapeAnalysis.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + if (steps.contains(OptionalStep.CONST_COLLECTOR)) { + ConstCollector.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + // dead code remover must be run before phi type resolver + if (needsDeadCodeRemover) { + DeadCodeRemover.process(ssaMeth); + } + + PhiTypeResolver.process(ssaMeth); + } + + public static SsaMethod debugEdgeSplit(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + return SsaConverter.testEdgeSplit(rmeth, paramWidth, isStatic); + } + + public static SsaMethod debugPhiPlacement(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + return SsaConverter.testPhiPlacement(rmeth, paramWidth, isStatic); + } + + public static SsaMethod debugRenaming(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + return SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + } + + public static SsaMethod debugDeadCodeRemover(RopMethod rmeth, + int paramWidth, boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + SsaMethod ssaMeth; + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + DeadCodeRemover.process(ssaMeth); + + return ssaMeth; + } + + public static SsaMethod debugNoRegisterAllocation(RopMethod rmeth, + int paramWidth, boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice, EnumSet steps) { + + SsaMethod ssaMeth; + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + + runSsaFormSteps(ssaMeth, steps); + + LivenessAnalyzer.constructInterferenceGraph(ssaMeth); + + return ssaMeth; + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/PhiInsn.java b/dexlib/src/main/java/com/android/dx/ssa/PhiInsn.java new file mode 100644 index 000000000..0a398642c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/PhiInsn.java @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import com.android.dx.util.Hex; +import java.util.ArrayList; +import java.util.List; + +/** + * A Phi instruction (magical post-control-flow-merge) instruction + * in SSA form. Will be converted to moves in predecessor blocks before + * conversion back to ROP form. + */ +public final class PhiInsn extends SsaInsn { + /** + * result register. The original result register of the phi insn + * is needed during the renaming process after the new result + * register has already been chosen. + */ + private final int ropResultReg; + + /** + * {@code non-null;} operands of the instruction; built up by + * {@link #addPhiOperand} + */ + private final ArrayList operands = new ArrayList(); + + /** {@code null-ok;} source registers; constructed lazily */ + private RegisterSpecList sources; + + /** + * Constructs a new phi insn with no operands. + * + * @param resultReg the result reg for this phi insn + * @param block block containing this insn. + */ + public PhiInsn(RegisterSpec resultReg, SsaBasicBlock block) { + super(resultReg, block); + ropResultReg = resultReg.getReg(); + } + + /** + * Makes a phi insn with a void result type. + * + * @param resultReg the result register for this phi insn. + * @param block block containing this insn. + */ + public PhiInsn(final int resultReg, final SsaBasicBlock block) { + /* + * The result type here is bogus: The type depends on the + * operand and will be derived later. + */ + super(RegisterSpec.make(resultReg, Type.VOID), block); + ropResultReg = resultReg; + } + + /** {@inheritDoc} */ + @Override + public PhiInsn clone() { + throw new UnsupportedOperationException("can't clone phi"); + } + + /** + * Updates the TypeBearers of all the sources (phi operands) to be + * the current TypeBearer of the register-defining instruction's result. + * This is used during phi-type resolution.

      + * + * Note that local association of operands are preserved in this step. + * + * @param ssaMeth method that contains this insn + */ + public void updateSourcesToDefinitions(SsaMethod ssaMeth) { + for (Operand o : operands) { + RegisterSpec def + = ssaMeth.getDefinitionForRegister( + o.regSpec.getReg()).getResult(); + + o.regSpec = o.regSpec.withType(def.getType()); + } + + sources = null; + } + + /** + * Changes the result type. Used during phi type resolution + * + * @param type {@code non-null;} new TypeBearer + * @param local {@code null-ok;} new local info, if available + */ + public void changeResultType(TypeBearer type, LocalItem local) { + setResult(RegisterSpec.makeLocalOptional( + getResult().getReg(), type, local)); + } + + /** + * Gets the original rop-form result reg. This is useful during renaming. + * + * @return the original rop-form result reg + */ + public int getRopResultReg() { + return ropResultReg; + } + + /** + * Adds an operand to this phi instruction. + * + * @param registerSpec register spec, including type and reg of operand + * @param predBlock predecessor block to be associated with this operand + */ + public void addPhiOperand(RegisterSpec registerSpec, + SsaBasicBlock predBlock) { + operands.add(new Operand(registerSpec, predBlock.getIndex(), + predBlock.getRopLabel())); + + // Un-cache sources, in case someone has already called getSources(). + sources = null; + } + + /** + * Removes all operand uses of a register from this phi instruction. + * + * @param registerSpec register spec, including type and reg of operand + */ + public void removePhiRegister(RegisterSpec registerSpec) { + ArrayList operandsToRemove = new ArrayList(); + for (Operand o : operands) { + if (o.regSpec.getReg() == registerSpec.getReg()) { + operandsToRemove.add(o); + } + } + + operands.removeAll(operandsToRemove); + + // Un-cache sources, in case someone has already called getSources(). + sources = null; + } + + /** + * Gets the index of the pred block associated with the RegisterSpec + * at the particular getSources() index. + * + * @param sourcesIndex index of source in getSources() + * @return block index + */ + public int predBlockIndexForSourcesIndex(int sourcesIndex) { + return operands.get(sourcesIndex).blockIndex; + } + + /** + * {@inheritDoc} + * + * Always returns null for {@code PhiInsn}s. + */ + @Override + public Rop getOpcode() { + return null; + } + + /** + * {@inheritDoc} + * + * Always returns null for {@code PhiInsn}s. + */ + @Override + public Insn getOriginalRopInsn() { + return null; + } + + /** + * {@inheritDoc} + * + * Always returns false for {@code PhiInsn}s. + */ + @Override + public boolean canThrow() { + return false; + } + + /** + * Gets sources. Constructed lazily from phi operand data structures and + * then cached. + * + * @return {@code non-null;} sources list + */ + @Override + public RegisterSpecList getSources() { + if (sources != null) { + return sources; + } + + if (operands.size() == 0) { + // How'd this happen? A phi insn with no operand? + return RegisterSpecList.EMPTY; + } + + int szSources = operands.size(); + sources = new RegisterSpecList(szSources); + + for (int i = 0; i < szSources; i++) { + Operand o = operands.get(i); + + sources.set(i, o.regSpec); + } + + sources.setImmutable(); + return sources; + } + + /** {@inheritDoc} */ + @Override + public boolean isRegASource(int reg) { + /* + * Avoid creating a sources list in case it has not already been + * created. + */ + + for (Operand o : operands) { + if (o.regSpec.getReg() == reg) { + return true; + } + } + + return false; + } + + /** + * @return true if all operands use the same register + */ + public boolean areAllOperandsEqual() { + if (operands.size() == 0 ) { + // This should never happen. + return true; + } + + int firstReg = operands.get(0).regSpec.getReg(); + for (Operand o : operands) { + if (firstReg != o.regSpec.getReg()) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void mapSourceRegisters(RegisterMapper mapper) { + for (Operand o : operands) { + RegisterSpec old = o.regSpec; + o.regSpec = mapper.map(old); + if (old != o.regSpec) { + getBlock().getParent().onSourceChanged(this, old, o.regSpec); + } + } + sources = null; + } + + /** + * Always throws an exeption, since a phi insn may not be + * converted back to rop form. + * + * @return always throws exception + */ + @Override + public Insn toRopInsn() { + throw new IllegalArgumentException( + "Cannot convert phi insns to rop form"); + } + + /** + * Returns the list of predecessor blocks associated with all operands + * that have {@code reg} as an operand register. + * + * @param reg register to look up + * @param ssaMeth method we're operating on + * @return list of predecessor blocks, empty if none + */ + public List predBlocksForReg(int reg, SsaMethod ssaMeth) { + ArrayList ret = new ArrayList(); + + for (Operand o : operands) { + if (o.regSpec.getReg() == reg) { + ret.add(ssaMeth.getBlocks().get(o.blockIndex)); + } + } + + return ret; + } + + /** {@inheritDoc} */ + @Override + public boolean isPhiOrMove() { + return true; + } + + /** {@inheritDoc} */ + @Override + public boolean hasSideEffect() { + return Optimizer.getPreserveLocals() && getLocalAssignment() != null; + } + + /** {@inheritDoc} */ + @Override + public void accept(SsaInsn.Visitor v) { + v.visitPhiInsn(this); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toHumanWithInline(null); + } + + /** + * Returns human-readable string for listing dumps. This method + * allows sub-classes to specify extra text. + * + * @param extra {@code null-ok;} the argument to print after the opcode + * @return human-readable string for listing dumps + */ + protected final String toHumanWithInline(String extra) { + StringBuffer sb = new StringBuffer(80); + + sb.append(SourcePosition.NO_INFO); + sb.append(": phi"); + + if (extra != null) { + sb.append("("); + sb.append(extra); + sb.append(")"); + } + + RegisterSpec result = getResult(); + + if (result == null) { + sb.append(" ."); + } else { + sb.append(" "); + sb.append(result.toHuman()); + } + + sb.append(" <-"); + + int sz = getSources().size(); + if (sz == 0) { + sb.append(" ."); + } else { + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(sources.get(i).toHuman() + + "[b=" + + Hex.u2(operands.get(i).ropLabel) + "]"); + } + } + + return sb.toString(); + } + + /** + * A single phi operand, consiting of source register and block index + * for move. + */ + private static class Operand { + public RegisterSpec regSpec; + public final int blockIndex; + public final int ropLabel; // only used for debugging + + public Operand(RegisterSpec regSpec, int blockIndex, int ropLabel) { + this.regSpec = regSpec; + this.blockIndex = blockIndex; + this.ropLabel = ropLabel; + } + } + + /** + * Visitor interface for instances of this (outer) class. + */ + public static interface Visitor { + public void visitPhiInsn(PhiInsn insn); + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/PhiTypeResolver.java b/dexlib/src/main/java/com/android/dx/ssa/PhiTypeResolver.java new file mode 100644 index 000000000..1b2ce9fe5 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/PhiTypeResolver.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.cf.code.Merger; +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.type.Type; +import com.android.dx.rop.type.TypeBearer; +import java.util.BitSet; +import java.util.List; + +/** + * Resolves the result types of phi instructions. When phi instructions + * are inserted, their result types are set to BT_VOID (which is a nonsensical + * type for a register) but must be resolve to a real type before converting + * out of SSA form.

      + * + * The resolve is done as an iterative merge of each phi's operand types. + * Phi operands may be themselves be the result of unresolved phis, + * and the algorithm tries to find the most-fit type (for example, if every + * operand is the same constant value or the same local variable info, we want + * that to be reflected).

      + * + * This algorithm assumes a dead-code remover has already removed all + * circular-only phis that may have been inserted. + */ +public class PhiTypeResolver { + + SsaMethod ssaMeth; + /** indexed by register; all registers still defined by unresolved phis */ + private final BitSet worklist; + + /** + * Resolves all phi types in the method + * @param ssaMeth method to process + */ + public static void process (SsaMethod ssaMeth) { + new PhiTypeResolver(ssaMeth).run(); + } + + private PhiTypeResolver(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + worklist = new BitSet(ssaMeth.getRegCount()); + } + + /** + * Runs the phi-type resolver. + */ + private void run() { + + int regCount = ssaMeth.getRegCount(); + + for (int reg = 0; reg < regCount; reg++) { + SsaInsn definsn = ssaMeth.getDefinitionForRegister(reg); + + if (definsn != null + && (definsn.getResult().getBasicType() == Type.BT_VOID)) { + worklist.set(reg); + } + } + + int reg; + while ( 0 <= (reg = worklist.nextSetBit(0))) { + worklist.clear(reg); + + /* + * definitions on the worklist have a type of BT_VOID, which + * must have originated from a PhiInsn. + */ + PhiInsn definsn = (PhiInsn)ssaMeth.getDefinitionForRegister(reg); + + if (resolveResultType(definsn)) { + /* + * If the result type has changed, re-resolve all phis + * that use this. + */ + + List useList = ssaMeth.getUseListForRegister(reg); + + int sz = useList.size(); + for (int i = 0; i < sz; i++ ) { + SsaInsn useInsn = useList.get(i); + RegisterSpec resultReg = useInsn.getResult(); + if (resultReg != null && useInsn instanceof PhiInsn) { + worklist.set(resultReg.getReg()); + } + } + } + } + } + + /** + * Returns true if a and b are equal, whether + * or not either of them are null. + * @param a + * @param b + * @return true if equal + */ + private static boolean equalsHandlesNulls(LocalItem a, LocalItem b) { + return (a == b) || ((a != null) && a.equals(b)); + } + + /** + * Resolves the result of a phi insn based on its operands. The "void" + * type, which is a nonsensical type for a register, is used for + * registers defined by as-of-yet-unresolved phi operations. + * + * @return true if the result type changed, false if no change + */ + boolean resolveResultType(PhiInsn insn) { + insn.updateSourcesToDefinitions(ssaMeth); + + RegisterSpecList sources = insn.getSources(); + + // Start by finding the first non-void operand + RegisterSpec first = null; + int firstIndex = -1; + + int szSources = sources.size(); + for (int i = 0 ; i cfgWorklist; + /** Worklist of executed basic blocks with phis to be processed */ + private ArrayList cfgPhiWorklist; + /** Bitset containing bits for each block that has been found executable */ + private BitSet executableBlocks; + /** Worklist for SSA edges. This is a list of registers to process */ + private ArrayList ssaWorklist; + /** + * Worklist for SSA edges that represent varying values. It makes the + * algorithm much faster if you move all values to VARYING as fast as + * possible. + */ + private ArrayList varyingWorklist; + /** Worklist of potential branches to convert to gotos */ + private ArrayList branchWorklist; + + private SCCP(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + this.regCount = ssaMeth.getRegCount(); + this.latticeValues = new int[this.regCount]; + this.latticeConstants = new Constant[this.regCount]; + this.cfgWorklist = new ArrayList(); + this.cfgPhiWorklist = new ArrayList(); + this.executableBlocks = new BitSet(ssaMeth.getBlocks().size()); + this.ssaWorklist = new ArrayList(); + this.varyingWorklist = new ArrayList(); + this.branchWorklist = new ArrayList(); + for (int i = 0; i < this.regCount; i++) { + latticeValues[i] = TOP; + latticeConstants[i] = null; + } + } + + /** + * Performs sparse conditional constant propagation on a method. + * @param ssaMethod Method to process + */ + public static void process (SsaMethod ssaMethod) { + new SCCP(ssaMethod).run(); + } + + /** + * Adds a SSA basic block to the CFG worklist if it's unexecuted, or + * to the CFG phi worklist if it's already executed. + * @param ssaBlock Block to add + */ + private void addBlockToWorklist(SsaBasicBlock ssaBlock) { + if (!executableBlocks.get(ssaBlock.getIndex())) { + cfgWorklist.add(ssaBlock); + executableBlocks.set(ssaBlock.getIndex()); + } else { + cfgPhiWorklist.add(ssaBlock); + } + } + + /** + * Adds an SSA register's uses to the SSA worklist. + * @param reg SSA register + * @param latticeValue new lattice value for @param reg. + */ + private void addUsersToWorklist(int reg, int latticeValue) { + if (latticeValue == VARYING) { + for (SsaInsn insn : ssaMeth.getUseListForRegister(reg)) { + varyingWorklist.add(insn); + } + } else { + for (SsaInsn insn : ssaMeth.getUseListForRegister(reg)) { + ssaWorklist.add(insn); + } + } + } + + /** + * Sets a lattice value for a register to value. + * @param reg SSA register + * @param value Lattice value + * @param cst Constant value (may be null) + * @return true if the lattice value changed. + */ + private boolean setLatticeValueTo(int reg, int value, Constant cst) { + if (value != CONSTANT) { + if (latticeValues[reg] != value) { + latticeValues[reg] = value; + return true; + } + return false; + } else { + if (latticeValues[reg] != value + || !latticeConstants[reg].equals(cst)) { + latticeValues[reg] = value; + latticeConstants[reg] = cst; + return true; + } + return false; + } + } + + /** + * Simulates a PHI node and set the lattice for the result + * to the appropriate value. + * Meet values: + * TOP x anything = TOP + * VARYING x anything = VARYING + * CONSTANT x CONSTANT = CONSTANT if equal constants, VARYING otherwise + * @param insn PHI to simulate. + */ + private void simulatePhi(PhiInsn insn) { + int phiResultReg = insn.getResult().getReg(); + + if (latticeValues[phiResultReg] == VARYING) { + return; + } + + RegisterSpecList sources = insn.getSources(); + int phiResultValue = TOP; + Constant phiConstant = null; + int sourceSize = sources.size(); + + for (int i = 0; i < sourceSize; i++) { + int predBlockIndex = insn.predBlockIndexForSourcesIndex(i); + int sourceReg = sources.get(i).getReg(); + int sourceRegValue = latticeValues[sourceReg]; + + if (!executableBlocks.get(predBlockIndex)) { + continue; + } + + if (sourceRegValue == CONSTANT) { + if (phiConstant == null) { + phiConstant = latticeConstants[sourceReg]; + phiResultValue = CONSTANT; + } else if (!latticeConstants[sourceReg].equals(phiConstant)){ + phiResultValue = VARYING; + break; + } + } else { + phiResultValue = sourceRegValue; + break; + } + } + if (setLatticeValueTo(phiResultReg, phiResultValue, phiConstant)) { + addUsersToWorklist(phiResultReg, phiResultValue); + } + } + + /** + * Simulate a block and note the results in the lattice. + * @param block Block to visit + */ + private void simulateBlock(SsaBasicBlock block) { + for (SsaInsn insn : block.getInsns()) { + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn) insn); + } else { + simulateStmt(insn); + } + } + } + + /** + * Simulate the phis in a block and note the results in the lattice. + * @param block Block to visit + */ + private void simulatePhiBlock(SsaBasicBlock block) { + for (SsaInsn insn : block.getInsns()) { + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn) insn); + } else { + return; + } + } + } + + private static String latticeValName(int latticeVal) { + switch (latticeVal) { + case TOP: return "TOP"; + case CONSTANT: return "CONSTANT"; + case VARYING: return "VARYING"; + default: return "UNKNOWN"; + } + } + + /** + * Simulates branch insns, if possible. Adds reachable successor blocks + * to the CFG worklists. + * @param insn branch to simulate + */ + private void simulateBranch(SsaInsn insn) { + Rop opcode = insn.getOpcode(); + RegisterSpecList sources = insn.getSources(); + + boolean constantBranch = false; + boolean constantSuccessor = false; + + // Check if the insn is a branch with a constant condition + if (opcode.getBranchingness() == Rop.BRANCH_IF) { + Constant cA = null; + Constant cB = null; + + RegisterSpec specA = sources.get(0); + int regA = specA.getReg(); + if (!ssaMeth.isRegALocal(specA) && + latticeValues[regA] == CONSTANT) { + cA = latticeConstants[regA]; + } + + if (sources.size() == 2) { + RegisterSpec specB = sources.get(1); + int regB = specB.getReg(); + if (!ssaMeth.isRegALocal(specB) && + latticeValues[regB] == CONSTANT) { + cB = latticeConstants[regB]; + } + } + + // Calculate the result of the condition + if (cA != null && sources.size() == 1) { + switch (((TypedConstant) cA).getBasicType()) { + case Type.BT_INT: + constantBranch = true; + int vA = ((CstInteger) cA).getValue(); + switch (opcode.getOpcode()) { + case RegOps.IF_EQ: + constantSuccessor = (vA == 0); + break; + case RegOps.IF_NE: + constantSuccessor = (vA != 0); + break; + case RegOps.IF_LT: + constantSuccessor = (vA < 0); + break; + case RegOps.IF_GE: + constantSuccessor = (vA >= 0); + break; + case RegOps.IF_LE: + constantSuccessor = (vA <= 0); + break; + case RegOps.IF_GT: + constantSuccessor = (vA > 0); + break; + default: + throw new RuntimeException("Unexpected op"); + } + break; + default: + // not yet supported + } + } else if (cA != null && cB != null) { + switch (((TypedConstant) cA).getBasicType()) { + case Type.BT_INT: + constantBranch = true; + int vA = ((CstInteger) cA).getValue(); + int vB = ((CstInteger) cB).getValue(); + switch (opcode.getOpcode()) { + case RegOps.IF_EQ: + constantSuccessor = (vA == vB); + break; + case RegOps.IF_NE: + constantSuccessor = (vA != vB); + break; + case RegOps.IF_LT: + constantSuccessor = (vA < vB); + break; + case RegOps.IF_GE: + constantSuccessor = (vA >= vB); + break; + case RegOps.IF_LE: + constantSuccessor = (vA <= vB); + break; + case RegOps.IF_GT: + constantSuccessor = (vA > vB); + break; + default: + throw new RuntimeException("Unexpected op"); + } + break; + default: + // not yet supported + } + } + } + + /* + * If condition is constant, add only the target block to the + * worklist. Otherwise, add all successors to the worklist. + */ + SsaBasicBlock block = insn.getBlock(); + + if (constantBranch) { + int successorBlock; + if (constantSuccessor) { + successorBlock = block.getSuccessorList().get(1); + } else { + successorBlock = block.getSuccessorList().get(0); + } + addBlockToWorklist(ssaMeth.getBlocks().get(successorBlock)); + branchWorklist.add(insn); + } else { + for (int i = 0; i < block.getSuccessorList().size(); i++) { + int successorBlock = block.getSuccessorList().get(i); + addBlockToWorklist(ssaMeth.getBlocks().get(successorBlock)); + } + } + } + + /** + * Simulates math insns, if possible. + * + * @param insn non-null insn to simulate + * @param resultType basic type of the result + * @return constant result or null if not simulatable. + */ + private Constant simulateMath(SsaInsn insn, int resultType) { + Insn ropInsn = insn.getOriginalRopInsn(); + int opcode = insn.getOpcode().getOpcode(); + RegisterSpecList sources = insn.getSources(); + int regA = sources.get(0).getReg(); + Constant cA; + Constant cB; + + if (latticeValues[regA] != CONSTANT) { + cA = null; + } else { + cA = latticeConstants[regA]; + } + + if (sources.size() == 1) { + CstInsn cstInsn = (CstInsn) ropInsn; + cB = cstInsn.getConstant(); + } else { /* sources.size() == 2 */ + int regB = sources.get(1).getReg(); + if (latticeValues[regB] != CONSTANT) { + cB = null; + } else { + cB = latticeConstants[regB]; + } + } + + if (cA == null || cB == null) { + //TODO handle a constant of 0 with MUL or AND + return null; + } + + switch (resultType) { + case Type.BT_INT: + int vR; + boolean skip=false; + + int vA = ((CstInteger) cA).getValue(); + int vB = ((CstInteger) cB).getValue(); + + switch (opcode) { + case RegOps.ADD: + vR = vA + vB; + break; + case RegOps.SUB: + // 1 source for reverse sub, 2 sources for regular sub + if (sources.size() == 1) { + vR = vB - vA; + } else { + vR = vA - vB; + } + break; + case RegOps.MUL: + vR = vA * vB; + break; + case RegOps.DIV: + if (vB == 0) { + skip = true; + vR = 0; // just to hide a warning + } else { + vR = vA / vB; + } + break; + case RegOps.AND: + vR = vA & vB; + break; + case RegOps.OR: + vR = vA | vB; + break; + case RegOps.XOR: + vR = vA ^ vB; + break; + case RegOps.SHL: + vR = vA << vB; + break; + case RegOps.SHR: + vR = vA >> vB; + break; + case RegOps.USHR: + vR = vA >>> vB; + break; + case RegOps.REM: + if (vB == 0) { + skip = true; + vR = 0; // just to hide a warning + } else { + vR = vA % vB; + } + break; + default: + throw new RuntimeException("Unexpected op"); + } + + return skip ? null : CstInteger.make(vR); + + default: + // not yet supported + return null; + } + } + + /** + * Simulates a statement and set the result lattice value. + * @param insn instruction to simulate + */ + private void simulateStmt(SsaInsn insn) { + Insn ropInsn = insn.getOriginalRopInsn(); + if (ropInsn.getOpcode().getBranchingness() != Rop.BRANCH_NONE + || ropInsn.getOpcode().isCallLike()) { + simulateBranch(insn); + } + + int opcode = insn.getOpcode().getOpcode(); + RegisterSpec result = insn.getResult(); + + if (result == null) { + // Find move-result-pseudo result for int div and int rem + if (opcode == RegOps.DIV || opcode == RegOps.REM) { + SsaBasicBlock succ = insn.getBlock().getPrimarySuccessor(); + result = succ.getInsns().get(0).getResult(); + } else { + return; + } + } + + int resultReg = result.getReg(); + int resultValue = VARYING; + Constant resultConstant = null; + + switch (opcode) { + case RegOps.CONST: { + CstInsn cstInsn = (CstInsn)ropInsn; + resultValue = CONSTANT; + resultConstant = cstInsn.getConstant(); + break; + } + case RegOps.MOVE: { + if (insn.getSources().size() == 1) { + int sourceReg = insn.getSources().get(0).getReg(); + resultValue = latticeValues[sourceReg]; + resultConstant = latticeConstants[sourceReg]; + } + break; + } + case RegOps.ADD: + case RegOps.SUB: + case RegOps.MUL: + case RegOps.DIV: + case RegOps.AND: + case RegOps.OR: + case RegOps.XOR: + case RegOps.SHL: + case RegOps.SHR: + case RegOps.USHR: + case RegOps.REM: { + resultConstant = simulateMath(insn, result.getBasicType()); + if (resultConstant != null) { + resultValue = CONSTANT; + } + break; + } + case RegOps.MOVE_RESULT_PSEUDO: { + if (latticeValues[resultReg] == CONSTANT) { + resultValue = latticeValues[resultReg]; + resultConstant = latticeConstants[resultReg]; + } + break; + } + // TODO: Handle non-int arithmetic. + // TODO: Eliminate check casts that we can prove the type of. + default: {} + } + if (setLatticeValueTo(resultReg, resultValue, resultConstant)) { + addUsersToWorklist(resultReg, resultValue); + } + } + + private void run() { + SsaBasicBlock firstBlock = ssaMeth.getEntryBlock(); + addBlockToWorklist(firstBlock); + + /* Empty all the worklists by propagating our values */ + while (!cfgWorklist.isEmpty() + || !cfgPhiWorklist.isEmpty() + || !ssaWorklist.isEmpty() + || !varyingWorklist.isEmpty()) { + while (!cfgWorklist.isEmpty()) { + int listSize = cfgWorklist.size() - 1; + SsaBasicBlock block = cfgWorklist.remove(listSize); + simulateBlock(block); + } + + while (!cfgPhiWorklist.isEmpty()) { + int listSize = cfgPhiWorklist.size() - 1; + SsaBasicBlock block = cfgPhiWorklist.remove(listSize); + simulatePhiBlock(block); + } + + while (!varyingWorklist.isEmpty()) { + int listSize = varyingWorklist.size() - 1; + SsaInsn insn = varyingWorklist.remove(listSize); + + if (!executableBlocks.get(insn.getBlock().getIndex())) { + continue; + } + + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn)insn); + } else { + simulateStmt(insn); + } + } + while (!ssaWorklist.isEmpty()) { + int listSize = ssaWorklist.size() - 1; + SsaInsn insn = ssaWorklist.remove(listSize); + + if (!executableBlocks.get(insn.getBlock().getIndex())) { + continue; + } + + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn)insn); + } else { + simulateStmt(insn); + } + } + } + + replaceConstants(); + replaceBranches(); + } + + /** + * Replaces TypeBearers in source register specs with constant type + * bearers if possible. These are then referenced in later optimization + * steps. + */ + private void replaceConstants() { + for (int reg = 0; reg < regCount; reg++) { + if (latticeValues[reg] != CONSTANT) { + continue; + } + if (!(latticeConstants[reg] instanceof TypedConstant)) { + // We can't do much with these + continue; + } + + SsaInsn defn = ssaMeth.getDefinitionForRegister(reg); + TypeBearer typeBearer = defn.getResult().getTypeBearer(); + + if (typeBearer.isConstant()) { + /* + * The definition was a constant already. + * The uses should be as well. + */ + continue; + } + + // Update the destination RegisterSpec with the constant value + RegisterSpec dest = defn.getResult(); + RegisterSpec newDest + = dest.withType((TypedConstant)latticeConstants[reg]); + defn.setResult(newDest); + + /* + * Update the sources RegisterSpec's of all non-move uses. + * These will be used in later steps. + */ + for (SsaInsn insn : ssaMeth.getUseListForRegister(reg)) { + if (insn.isPhiOrMove()) { + continue; + } + + NormalSsaInsn nInsn = (NormalSsaInsn) insn; + RegisterSpecList sources = insn.getSources(); + + int index = sources.indexOfRegister(reg); + + RegisterSpec spec = sources.get(index); + RegisterSpec newSpec + = spec.withType((TypedConstant)latticeConstants[reg]); + + nInsn.changeOneSource(index, newSpec); + } + } + } + + /** + * Replaces branches that have constant conditions with gotos + */ + private void replaceBranches() { + for (SsaInsn insn : branchWorklist) { + // Find if a successor block is never executed + int oldSuccessor = -1; + SsaBasicBlock block = insn.getBlock(); + int successorSize = block.getSuccessorList().size(); + for (int i = 0; i < successorSize; i++) { + int successorBlock = block.getSuccessorList().get(i); + if (!executableBlocks.get(successorBlock)) { + oldSuccessor = successorBlock; + } + } + + /* + * Prune branches that have already been handled and ones that no + * longer have constant conditions (no nonexecutable successors) + */ + if (successorSize != 2 || oldSuccessor == -1) continue; + + // Replace branch with goto + Insn originalRopInsn = insn.getOriginalRopInsn(); + block.replaceLastInsn(new PlainInsn(Rops.GOTO, + originalRopInsn.getPosition(), null, RegisterSpecList.EMPTY)); + block.removeSuccessor(oldSuccessor); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/SetFactory.java b/dexlib/src/main/java/com/android/dx/ssa/SetFactory.java new file mode 100644 index 000000000..92e965fb9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/SetFactory.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.util.BitIntSet; +import com.android.dx.util.IntSet; +import com.android.dx.util.ListIntSet; + + +/** + * Makes int sets for various parts of the optimizer. + */ +public final class SetFactory { + + /** + * BitIntSet/ListIntSet threshold for dominance frontier sets. These + * sets are kept per basic block until phi placement and tend to be, + * like the CFG itself, very sparse at large sizes. + * + * A value of 3072 here is somewhere around 1.125mb of total bitset size. + */ + private static final int DOMFRONT_SET_THRESHOLD_SIZE = 3072; + + /** + * BitIntSet/ListIntSet threshold for interference graph sets. These + * sets are kept per register until register allocation is done. + * + * A value of 3072 here is somewhere around 1.125mb of total bitset size. + */ + private static final int INTERFERENCE_SET_THRESHOLD_SIZE = 3072; + + /** + * BitIntSet/ListIntSet threshold for the live in/out sets kept by + * {@link SsaBasicBlock}. These are sets of SSA registers kept per basic + * block during register allocation. + * + * The total size of a bitset for this would be the count of blocks + * times the size of registers. The threshold value here is merely + * the register count, which is typically on the order of the block + * count as well. + */ + private static final int LIVENESS_SET_THRESHOLD_SIZE = 3072; + + + /** + * Make IntSet for the dominance-frontier sets. + * + * @param szBlocks {@code >=0;} count of basic blocks in method + * @return {@code non-null;} appropriate set + */ + /*package*/ static IntSet makeDomFrontSet(int szBlocks) { + return szBlocks <= DOMFRONT_SET_THRESHOLD_SIZE + ? new BitIntSet(szBlocks) + : new ListIntSet(); + } + + /** + * Make IntSet for the interference graph sets. Public because + * InterferenceGraph is in another package. + * + * @param countRegs {@code >=0;} count of SSA registers used in method + * @return {@code non-null;} appropriate set + */ + public static IntSet makeInterferenceSet(int countRegs) { + return countRegs <= INTERFERENCE_SET_THRESHOLD_SIZE + ? new BitIntSet(countRegs) + : new ListIntSet(); + } + + /** + * Make IntSet for register live in/out sets. + * + * @param countRegs {@code >=0;} count of SSA registers used in method + * @return {@code non-null;} appropriate set + */ + /*package*/ static IntSet makeLivenessSet(int countRegs) { + return countRegs <= LIVENESS_SET_THRESHOLD_SIZE + ? new BitIntSet(countRegs) + : new ListIntSet(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/SsaBasicBlock.java b/dexlib/src/main/java/com/android/dx/ssa/SsaBasicBlock.java new file mode 100644 index 000000000..56d8c8cd1 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/SsaBasicBlock.java @@ -0,0 +1,1031 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.BasicBlock; +import com.android.dx.rop.code.BasicBlockList; +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.InsnList; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.util.Hex; +import com.android.dx.util.IntList; +import com.android.dx.util.IntSet; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * An SSA representation of a basic block. + */ +public final class SsaBasicBlock { + /** + * {@code non-null;} comparator for instances of this class that + * just compares block labels + */ + public static final Comparator LABEL_COMPARATOR = + new LabelComparator(); + + /** {@code non-null;} insn list associated with this instance */ + private ArrayList insns; + + /** {@code non-null;} predecessor set (by block list index) */ + private BitSet predecessors; + + /** {@code non-null;} successor set (by block list index) */ + private BitSet successors; + + /** + * {@code non-null;} ordered successor list + * (same block may be listed more than once) + */ + private IntList successorList; + + /** + * block list index of primary successor, or {@code -1} for no primary + * successor + */ + private int primarySuccessor = -1; + + /** label of block in rop form */ + private int ropLabel; + + /** {@code non-null;} method we belong to */ + private SsaMethod parent; + + /** our index into parent.getBlock() */ + private int index; + + /** list of dom children */ + private final ArrayList domChildren; + + /** + * the number of moves added to the end of the block during the + * phi-removal process. Retained for subsequent move scheduling. + */ + private int movesFromPhisAtEnd = 0; + + /** + * the number of moves added to the beginning of the block during the + * phi-removal process. Retained for subsequent move scheduling. + */ + private int movesFromPhisAtBeginning = 0; + + /** + * contains last computed value of reachability of this block, or -1 + * if reachability hasn't been calculated yet + */ + private int reachable = -1; + + /** + * {@code null-ok;} indexed by reg: the regs that are live-in at + * this block + */ + private IntSet liveIn; + + /** + * {@code null-ok;} indexed by reg: the regs that are live-out at + * this block + */ + private IntSet liveOut; + + /** + * Creates a new empty basic block. + * + * @param basicBlockIndex index this block will have + * @param ropLabel original rop-form label + * @param parent method of this block + */ + public SsaBasicBlock(final int basicBlockIndex, final int ropLabel, + final SsaMethod parent) { + this.parent = parent; + this.index = basicBlockIndex; + this.insns = new ArrayList(); + this.ropLabel = ropLabel; + + this.predecessors = new BitSet(parent.getBlocks().size()); + this.successors = new BitSet(parent.getBlocks().size()); + this.successorList = new IntList(); + + domChildren = new ArrayList(); + } + + /** + * Creates a new SSA basic block from a ROP form basic block. + * + * @param rmeth original method + * @param basicBlockIndex index this block will have + * @param parent method of this block predecessor set will be + * updated + * @return new instance + */ + public static SsaBasicBlock newFromRop(RopMethod rmeth, + int basicBlockIndex, final SsaMethod parent) { + BasicBlockList ropBlocks = rmeth.getBlocks(); + BasicBlock bb = ropBlocks.get(basicBlockIndex); + SsaBasicBlock result = + new SsaBasicBlock(basicBlockIndex, bb.getLabel(), parent); + InsnList ropInsns = bb.getInsns(); + + result.insns.ensureCapacity(ropInsns.size()); + + for (int i = 0, sz = ropInsns.size() ; i < sz ; i++) { + result.insns.add(new NormalSsaInsn (ropInsns.get(i), result)); + } + + result.predecessors = SsaMethod.bitSetFromLabelList( + ropBlocks, + rmeth.labelToPredecessors(bb.getLabel())); + + result.successors + = SsaMethod.bitSetFromLabelList(ropBlocks, bb.getSuccessors()); + + result.successorList + = SsaMethod.indexListFromLabelList(ropBlocks, + bb.getSuccessors()); + + if (result.successorList.size() != 0) { + int primarySuccessor = bb.getPrimarySuccessor(); + + result.primarySuccessor = (primarySuccessor < 0) + ? -1 : ropBlocks.indexOfLabel(primarySuccessor); + } + + return result; + } + + /** + * Adds a basic block as a dom child for this block. Used when constructing + * the dom tree. + * + * @param child {@code non-null;} new dom child + */ + public void addDomChild(SsaBasicBlock child) { + domChildren.add(child); + } + + /** + * Gets the dom children for this node. Don't modify this list. + * + * @return {@code non-null;} list of dom children + */ + public ArrayList getDomChildren() { + return domChildren; + } + + /** + * Adds a phi insn to the beginning of this block. The result type of + * the phi will be set to void, to indicate that it's currently unknown. + * + * @param reg {@code >=0;} result reg + */ + public void addPhiInsnForReg(int reg) { + insns.add(0, new PhiInsn(reg, this)); + } + + /** + * Adds a phi insn to the beginning of this block. This is to be used + * when the result type or local-association can be determined at phi + * insert time. + * + * @param resultSpec {@code non-null;} reg + */ + public void addPhiInsnForReg(RegisterSpec resultSpec) { + insns.add(0, new PhiInsn(resultSpec, this)); + } + + /** + * Adds an insn to the head of this basic block, just after any phi + * insns. + * + * @param insn {@code non-null;} rop-form insn to add + */ + public void addInsnToHead(Insn insn) { + SsaInsn newInsn = SsaInsn.makeFromRop(insn, this); + insns.add(getCountPhiInsns(), newInsn); + parent.onInsnAdded(newInsn); + } + + /** + * Replaces the last insn in this block. The provided insn must have + * some branchingness. + * + * @param insn {@code non-null;} rop-form insn to add, which must branch. + */ + public void replaceLastInsn(Insn insn) { + if (insn.getOpcode().getBranchingness() == Rop.BRANCH_NONE) { + throw new IllegalArgumentException("last insn must branch"); + } + + SsaInsn oldInsn = insns.get(insns.size() - 1); + SsaInsn newInsn = SsaInsn.makeFromRop(insn, this); + + insns.set(insns.size() - 1, newInsn); + + parent.onInsnRemoved(oldInsn); + parent.onInsnAdded(newInsn); + } + + /** + * Visits each phi insn. + * + * @param v {@code non-null;} the callback + */ + public void forEachPhiInsn(PhiInsn.Visitor v) { + int sz = insns.size(); + + for (int i = 0; i < sz; i++) { + SsaInsn insn = insns.get(i); + if (insn instanceof PhiInsn) { + v.visitPhiInsn((PhiInsn) insn); + } else { + /* + * Presently we assume PhiInsn's are in a continuous + * block at the top of the list + */ + break; + } + } + } + + /** + * Deletes all phi insns. Do this after adding appropriate move insns. + */ + public void removeAllPhiInsns() { + /* + * Presently we assume PhiInsn's are in a continuous + * block at the top of the list. + */ + + insns.subList(0, getCountPhiInsns()).clear(); + } + + /** + * Gets the number of phi insns at the top of this basic block. + * + * @return count of phi insns + */ + private int getCountPhiInsns() { + int countPhiInsns; + + int sz = insns.size(); + for (countPhiInsns = 0; countPhiInsns < sz; countPhiInsns++) { + SsaInsn insn = insns.get(countPhiInsns); + if (!(insn instanceof PhiInsn)) { + break; + } + } + + return countPhiInsns; + } + + /** + * @return {@code non-null;} the (mutable) instruction list for this block, + * with phi insns at the beginning + */ + public ArrayList getInsns() { + return insns; + } + + /** + * @return {@code non-null;} the (mutable) list of phi insns for this block + */ + public List getPhiInsns() { + return insns.subList(0, getCountPhiInsns()); + } + + /** + * @return the block index of this block + */ + public int getIndex() { + return index; + } + + /** + * @return the label of this block in rop form + */ + public int getRopLabel() { + return ropLabel; + } + + /** + * @return the label of this block in rop form as a hex string + */ + public String getRopLabelString() { + return Hex.u2(ropLabel); + } + + /** + * @return {@code non-null;} predecessors set, indexed by block index + */ + public BitSet getPredecessors() { + return predecessors; + } + + /** + * @return {@code non-null;} successors set, indexed by block index + */ + public BitSet getSuccessors() { + return successors; + } + + /** + * @return {@code non-null;} ordered successor list, containing block + * indicies + */ + public IntList getSuccessorList() { + return successorList; + } + + /** + * @return {@code >= -1;} block index of primary successor or + * {@code -1} if no primary successor + */ + public int getPrimarySuccessorIndex() { + return primarySuccessor; + } + + /** + * @return rop label of primary successor + */ + public int getPrimarySuccessorRopLabel() { + return parent.blockIndexToRopLabel(primarySuccessor); + } + + /** + * @return {@code null-ok;} the primary successor block or {@code null} + * if there is none + */ + public SsaBasicBlock getPrimarySuccessor() { + if (primarySuccessor < 0) { + return null; + } else { + return parent.getBlocks().get(primarySuccessor); + } + } + + /** + * @return successor list of rop labels + */ + public IntList getRopLabelSuccessorList() { + IntList result = new IntList(successorList.size()); + + int sz = successorList.size(); + + for (int i = 0; i < sz; i++) { + result.add(parent.blockIndexToRopLabel(successorList.get(i))); + } + return result; + } + + /** + * @return {@code non-null;} method that contains this block + */ + public SsaMethod getParent() { + return parent; + } + + /** + * Inserts a new empty GOTO block as a predecessor to this block. + * All previous predecessors will be predecessors to the new block. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public SsaBasicBlock insertNewPredecessor() { + SsaBasicBlock newPred = parent.makeNewGotoBlock(); + + // Update the new block. + newPred.predecessors = predecessors; + newPred.successors.set(index) ; + newPred.successorList.add(index); + newPred.primarySuccessor = index; + + + // Update us. + predecessors = new BitSet(parent.getBlocks().size()); + predecessors.set(newPred.index); + + // Update our (soon-to-be) old predecessors. + for (int i = newPred.predecessors.nextSetBit(0); i >= 0; + i = newPred.predecessors.nextSetBit(i + 1)) { + + SsaBasicBlock predBlock = parent.getBlocks().get(i); + + predBlock.replaceSuccessor(index, newPred.index); + } + + return newPred; + } + + /** + * Constructs and inserts a new empty GOTO block {@code Z} between + * this block ({@code A}) and a current successor block + * ({@code B}). The new block will replace B as A's successor and + * A as B's predecessor. A and B will no longer be directly connected. + * If B is listed as a successor multiple times, all references + * are replaced. + * + * @param other current successor (B) + * @return {@code non-null;} an appropriately-constructed instance + */ + public SsaBasicBlock insertNewSuccessor(SsaBasicBlock other) { + SsaBasicBlock newSucc = parent.makeNewGotoBlock(); + + if (!successors.get(other.index)) { + throw new RuntimeException("Block " + other.getRopLabelString() + + " not successor of " + getRopLabelString()); + } + + // Update the new block. + newSucc.predecessors.set(this.index); + newSucc.successors.set(other.index) ; + newSucc.successorList.add(other.index); + newSucc.primarySuccessor = other.index; + + // Update us. + for (int i = successorList.size() - 1 ; i >= 0; i--) { + if (successorList.get(i) == other.index) { + successorList.set(i, newSucc.index); + } + } + + if (primarySuccessor == other.index) { + primarySuccessor = newSucc.index; + } + successors.clear(other.index); + successors.set(newSucc.index); + + // Update "other". + other.predecessors.set(newSucc.index); + other.predecessors.set(index, successors.get(other.index)); + + return newSucc; + } + + /** + * Replaces an old successor with a new successor. This will throw + * RuntimeException if {@code oldIndex} was not a successor. + * + * @param oldIndex index of old successor block + * @param newIndex index of new successor block + */ + public void replaceSuccessor(int oldIndex, int newIndex) { + if (oldIndex == newIndex) { + return; + } + + // Update us. + successors.set(newIndex); + + if (primarySuccessor == oldIndex) { + primarySuccessor = newIndex; + } + + for (int i = successorList.size() - 1 ; i >= 0; i--) { + if (successorList.get(i) == oldIndex) { + successorList.set(i, newIndex); + } + } + + successors.clear(oldIndex); + + // Update new successor. + parent.getBlocks().get(newIndex).predecessors.set(index); + + // Update old successor. + parent.getBlocks().get(oldIndex).predecessors.clear(index); + } + + /** + * Removes a successor from this block's successor list. + * + * @param oldIndex index of successor block to remove + */ + public void removeSuccessor(int oldIndex) { + int removeIndex = 0; + + for (int i = successorList.size() - 1; i >= 0; i--) { + if (successorList.get(i) == oldIndex) { + removeIndex = i; + } else { + primarySuccessor = successorList.get(i); + } + } + + successorList.removeIndex(removeIndex); + successors.clear(oldIndex); + parent.getBlocks().get(oldIndex).predecessors.clear(index); + } + + /** + * Attaches block to an exit block if necessary. If this block + * is not an exit predecessor or is the exit block, this block does + * nothing. For use by {@link com.android.dx.ssa.SsaMethod#makeExitBlock} + * + * @param exitBlock {@code non-null;} exit block + */ + public void exitBlockFixup(SsaBasicBlock exitBlock) { + if (this == exitBlock) { + return; + } + + if (successorList.size() == 0) { + /* + * This is an exit predecessor. + * Set the successor to the exit block + */ + successors.set(exitBlock.index); + successorList.add(exitBlock.index); + primarySuccessor = exitBlock.index; + exitBlock.predecessors.set(this.index); + } + } + + /** + * Adds a move instruction to the end of this basic block, just + * before the last instruction. If the result of the final instruction + * is the source in question, then the move is placed at the beginning of + * the primary successor block. This is for unversioned registers. + * + * @param result move destination + * @param source move source + */ + public void addMoveToEnd(RegisterSpec result, RegisterSpec source) { + + if (result.getReg() == source.getReg()) { + // Sometimes we end up with no-op moves. Ignore them here. + return; + } + + /* + * The last Insn has to be a normal SSA insn: a phi can't branch + * or return or cause an exception, etc. + */ + NormalSsaInsn lastInsn; + lastInsn = (NormalSsaInsn)insns.get(insns.size()-1); + + if (lastInsn.getResult() != null || lastInsn.getSources().size() > 0) { + /* + * The final insn in this block has a source or result + * register, and the moves we may need to place and + * schedule may interfere. We need to insert this + * instruction at the beginning of the primary successor + * block instead. We know this is safe, because when we + * edge-split earlier, we ensured that each successor has + * only us as a predecessor. + */ + + for (int i = successors.nextSetBit(0) + ; i >= 0 + ; i = successors.nextSetBit(i + 1)) { + + SsaBasicBlock succ; + + succ = parent.getBlocks().get(i); + succ.addMoveToBeginning(result, source); + } + } else { + /* + * We can safely add a move to the end of the block just + * before the last instruction, because the final insn does + * not assign to anything. + */ + RegisterSpecList sources = RegisterSpecList.make(source); + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn(Rops.opMove(result.getType()), + SourcePosition.NO_INFO, result, sources), this); + + insns.add(insns.size() - 1, toAdd); + + movesFromPhisAtEnd++; + } + } + + /** + * Adds a move instruction after the phi insn block. + * + * @param result move destination + * @param source move source + */ + public void addMoveToBeginning (RegisterSpec result, RegisterSpec source) { + if (result.getReg() == source.getReg()) { + // Sometimes we end up with no-op moves. Ignore them here. + return; + } + + RegisterSpecList sources = RegisterSpecList.make(source); + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn(Rops.opMove(result.getType()), + SourcePosition.NO_INFO, result, sources), this); + + insns.add(getCountPhiInsns(), toAdd); + movesFromPhisAtBeginning++; + } + + /** + * Sets the register as used in a bitset, taking into account its + * category/width. + * + * @param regsUsed set, indexed by register number + * @param rs register to mark as used + */ + private static void setRegsUsed (BitSet regsUsed, RegisterSpec rs) { + regsUsed.set(rs.getReg()); + if (rs.getCategory() > 1) { + regsUsed.set(rs.getReg() + 1); + } + } + + /** + * Checks to see if the register is used in a bitset, taking + * into account its category/width. + * + * @param regsUsed set, indexed by register number + * @param rs register to mark as used + * @return true if register is fully or partially (for the case of wide + * registers) used. + */ + private static boolean checkRegUsed (BitSet regsUsed, RegisterSpec rs) { + int reg = rs.getReg(); + int category = rs.getCategory(); + + return regsUsed.get(reg) + || (category == 2 ? regsUsed.get(reg + 1) : false); + } + + /** + * Ensures that all move operations in this block occur such that + * reads of any register happen before writes to that register. + * NOTE: caller is expected to returnSpareRegisters()! + * + * TODO: See Briggs, et al "Practical Improvements to the Construction and + * Destruction of Static Single Assignment Form" section 5. a) This can + * be done in three passes. + * + * @param toSchedule List of instructions. Must consist only of moves. + */ + private void scheduleUseBeforeAssigned(List toSchedule) { + BitSet regsUsedAsSources = new BitSet(parent.getRegCount()); + + // TODO: Get rid of this. + BitSet regsUsedAsResults = new BitSet(parent.getRegCount()); + + int sz = toSchedule.size(); + + int insertPlace = 0; + + while (insertPlace < sz) { + int oldInsertPlace = insertPlace; + + // Record all registers used as sources in this block. + for (int i = insertPlace; i < sz; i++) { + setRegsUsed(regsUsedAsSources, + toSchedule.get(i).getSources().get(0)); + + setRegsUsed(regsUsedAsResults, + toSchedule.get(i).getResult()); + } + + /* + * If there are no circular dependencies, then there exists + * n instructions where n > 1 whose result is not used as a source. + */ + for (int i = insertPlace; i + * + * This is necessary because copy-propogation may have left us in a state + * where the same basic block has the same register as a phi operand + * and a result. In this case, the register in the phi operand always + * refers value before any other phis have executed. + */ + public void scheduleMovesFromPhis() { + if (movesFromPhisAtBeginning > 1) { + List toSchedule; + + toSchedule = insns.subList(0, movesFromPhisAtBeginning); + + scheduleUseBeforeAssigned(toSchedule); + + SsaInsn firstNonPhiMoveInsn = insns.get(movesFromPhisAtBeginning); + + /* + * TODO: It's actually possible that this case never happens, + * because a move-exception block, having only one predecessor + * in SSA form, perhaps is never on a dominance frontier. + */ + if (firstNonPhiMoveInsn.isMoveException()) { + if (true) { + /* + * We've yet to observe this case, and if it can + * occur the code written to handle it probably + * does not work. + */ + throw new RuntimeException( + "Unexpected: moves from " + +"phis before move-exception"); + } else { + /* + * A move-exception insn must be placed first in this block + * We need to move it there, and deal with possible + * interference. + */ + boolean moveExceptionInterferes = false; + + int moveExceptionResult + = firstNonPhiMoveInsn.getResult().getReg(); + + /* + * Does the move-exception result reg interfere with the + * phi moves? + */ + for (SsaInsn insn : toSchedule) { + if (insn.isResultReg(moveExceptionResult) + || insn.isRegASource(moveExceptionResult)) { + moveExceptionInterferes = true; + break; + } + } + + if (!moveExceptionInterferes) { + // This is the easy case. + insns.remove(movesFromPhisAtBeginning); + insns.add(0, firstNonPhiMoveInsn); + } else { + /* + * We need to move the result to a spare reg + * and move it back. + */ + RegisterSpec originalResultSpec + = firstNonPhiMoveInsn.getResult(); + int spareRegister = parent.borrowSpareRegister( + originalResultSpec.getCategory()); + + // We now move it to a spare register. + firstNonPhiMoveInsn.changeResultReg(spareRegister); + RegisterSpec tempSpec = + firstNonPhiMoveInsn.getResult(); + + insns.add(0, firstNonPhiMoveInsn); + + // And here we move it back. + + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn( + Rops.opMove(tempSpec.getType()), + SourcePosition.NO_INFO, + originalResultSpec, + RegisterSpecList.make(tempSpec)), + this); + + + /* + * Place it immediately after the phi-moves, + * overwriting the move-exception that was there. + */ + insns.set(movesFromPhisAtBeginning + 1, toAdd); + } + } + } + } + + if (movesFromPhisAtEnd > 1) { + scheduleUseBeforeAssigned( + insns.subList(insns.size() - movesFromPhisAtEnd - 1, + insns.size() - 1)); + } + + // Return registers borrowed here and in scheduleUseBeforeAssigned(). + parent.returnSpareRegisters(); + + } + + /** + * Visits all insns in this block. + * + * @param visitor {@code non-null;} callback interface + */ + public void forEachInsn(SsaInsn.Visitor visitor) { + // This gets called a LOT, and not using an iterator + // saves a lot of allocations and reduces memory usage + int len = insns.size(); + for (int i = 0; i < len; i++) { + insns.get(i).accept(visitor); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "{" + index + ":" + Hex.u2(ropLabel) + '}'; + } + + /** + * Visitor interface for basic blocks. + */ + public interface Visitor { + /** + * Indicates a block has been visited by an iterator method. + * + * @param v {@code non-null;} block visited + * @param parent {@code null-ok;} parent node if applicable + */ + void visitBlock (SsaBasicBlock v, SsaBasicBlock parent); + } + + /** + * Label comparator. + */ + public static final class LabelComparator + implements Comparator { + /** {@inheritDoc} */ + public int compare(SsaBasicBlock b1, SsaBasicBlock b2) { + int label1 = b1.ropLabel; + int label2 = b2.ropLabel; + + if (label1 < label2) { + return -1; + } else if (label1 > label2) { + return 1; + } else { + return 0; + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/SsaConverter.java b/dexlib/src/main/java/com/android/dx/ssa/SsaConverter.java new file mode 100644 index 000000000..1fd6f7825 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/SsaConverter.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.util.IntIterator; +import java.util.ArrayList; +import java.util.BitSet; + +/** + * Converts ROP methods to SSA Methods + */ +public class SsaConverter { + public static final boolean DEBUG = false; + + /** + * Returns an SSA representation, edge-split and with phi + * functions placed. + * + * @param rmeth input + * @param paramWidth the total width, in register-units, of the method's + * parameters + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + * @return output in SSA form + */ + public static SsaMethod convertToSsaMethod(RopMethod rmeth, + int paramWidth, boolean isStatic) { + SsaMethod result + = SsaMethod.newFromRopMethod(rmeth, paramWidth, isStatic); + + edgeSplit(result); + + LocalVariableInfo localInfo = LocalVariableExtractor.extract(result); + + placePhiFunctions(result, localInfo, 0); + new SsaRenamer(result).run(); + + /* + * The exit block, added here, is not considered for edge splitting + * or phi placement since no actual control flows to it. + */ + result.makeExitBlock(); + + return result; + } + + /** + * Updates an SSA representation, placing phi functions and renaming all + * registers above a certain threshold number. + * + * @param ssaMeth input + * @param threshold registers below this number are unchanged + */ + public static void updateSsaMethod(SsaMethod ssaMeth, int threshold) { + LocalVariableInfo localInfo = LocalVariableExtractor.extract(ssaMeth); + placePhiFunctions(ssaMeth, localInfo, threshold); + new SsaRenamer(ssaMeth, threshold).run(); + } + + /** + * Returns an SSA represention with only the edge-splitter run. + * + * @param rmeth method to process + * @param paramWidth width of all arguments in the method + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + * @return an SSA represention with only the edge-splitter run + */ + public static SsaMethod testEdgeSplit (RopMethod rmeth, int paramWidth, + boolean isStatic) { + SsaMethod result; + + result = SsaMethod.newFromRopMethod(rmeth, paramWidth, isStatic); + + edgeSplit(result); + return result; + } + + /** + * Returns an SSA represention with only the steps through the + * phi placement run. + * + * @param rmeth method to process + * @param paramWidth width of all arguments in the method + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + * @return an SSA represention with only the edge-splitter run + */ + public static SsaMethod testPhiPlacement (RopMethod rmeth, int paramWidth, + boolean isStatic) { + SsaMethod result; + + result = SsaMethod.newFromRopMethod(rmeth, paramWidth, isStatic); + + edgeSplit(result); + + LocalVariableInfo localInfo = LocalVariableExtractor.extract(result); + + placePhiFunctions(result, localInfo, 0); + return result; + } + + /** + * See Appel section 19.1: + * + * Converts CFG into "edge-split" form, such that each node either a + * unique successor or unique predecessor.

      + * + * In addition, the SSA form we use enforces a further constraint, + * requiring each block with a final instruction that returns a + * value to have a primary successor that has no other + * predecessor. This ensures move statements can always be + * inserted correctly when phi statements are removed. + * + * @param result method to process + */ + private static void edgeSplit(SsaMethod result) { + edgeSplitPredecessors(result); + edgeSplitMoveExceptionsAndResults(result); + edgeSplitSuccessors(result); + } + + /** + * Inserts Z nodes as new predecessors for every node that has multiple + * successors and multiple predecessors. + * + * @param result {@code non-null;} method to process + */ + private static void edgeSplitPredecessors(SsaMethod result) { + ArrayList blocks = result.getBlocks(); + + /* + * New blocks are added to the end of the block list during + * this iteration. + */ + for (int i = blocks.size() - 1; i >= 0; i-- ) { + SsaBasicBlock block = blocks.get(i); + if (nodeNeedsUniquePredecessor(block)) { + block.insertNewPredecessor(); + } + } + } + + /** + * @param block {@code non-null;} block in question + * @return {@code true} if this node needs to have a unique + * predecessor created for it + */ + private static boolean nodeNeedsUniquePredecessor(SsaBasicBlock block) { + /* + * Any block with that has both multiple successors and multiple + * predecessors needs a new predecessor node. + */ + + int countPredecessors = block.getPredecessors().cardinality(); + int countSuccessors = block.getSuccessors().cardinality(); + + return (countPredecessors > 1 && countSuccessors > 1); + } + + /** + * In ROP form, move-exception must occur as the first insn in a block + * immediately succeeding the insn that could thrown an exception. + * We may need room to insert move insns later, so make sure to split + * any block that starts with a move-exception such that there is a + * unique move-exception block for each predecessor. + * + * @param ssaMeth method to process + */ + private static void edgeSplitMoveExceptionsAndResults(SsaMethod ssaMeth) { + ArrayList blocks = ssaMeth.getBlocks(); + + /* + * New blocks are added to the end of the block list during + * this iteration. + */ + for (int i = blocks.size() - 1; i >= 0; i-- ) { + SsaBasicBlock block = blocks.get(i); + + /* + * Any block that starts with a move-exception and has more than + * one predecessor... + */ + if (!block.isExitBlock() + && block.getPredecessors().cardinality() > 1 + && block.getInsns().get(0).isMoveException()) { + + // block.getPredecessors() is changed in the loop below. + BitSet preds = (BitSet)block.getPredecessors().clone(); + for (int j = preds.nextSetBit(0); j >= 0; + j = preds.nextSetBit(j + 1)) { + SsaBasicBlock predecessor = blocks.get(j); + SsaBasicBlock zNode + = predecessor.insertNewSuccessor(block); + + /* + * Make sure to place the move-exception as the + * first insn. + */ + zNode.getInsns().add(0, block.getInsns().get(0).clone()); + } + + // Remove the move-exception from the original block. + block.getInsns().remove(0); + } + } + } + + /** + * Inserts Z nodes for every node that needs a new + * successor. + * + * @param result {@code non-null;} method to process + */ + private static void edgeSplitSuccessors(SsaMethod result) { + ArrayList blocks = result.getBlocks(); + + /* + * New blocks are added to the end of the block list during + * this iteration. + */ + for (int i = blocks.size() - 1; i >= 0; i-- ) { + SsaBasicBlock block = blocks.get(i); + + // Successors list is modified in loop below. + BitSet successors = (BitSet)block.getSuccessors().clone(); + for (int j = successors.nextSetBit(0); + j >= 0; j = successors.nextSetBit(j+1)) { + + SsaBasicBlock succ = blocks.get(j); + + if (needsNewSuccessor(block, succ)) { + block.insertNewSuccessor(succ); + } + } + } + } + + /** + * Returns {@code true} if block and successor need a Z-node + * between them. Presently, this is {@code true} if the final + * instruction has any sources or results and the current + * successor block has more than one predecessor. + * + * @param block predecessor node + * @param succ successor node + * @return {@code true} if a Z node is needed + */ + private static boolean needsNewSuccessor(SsaBasicBlock block, + SsaBasicBlock succ) { + ArrayList insns = block.getInsns(); + SsaInsn lastInsn = insns.get(insns.size() - 1); + + return ((lastInsn.getResult() != null) + || (lastInsn.getSources().size() > 0)) + && succ.getPredecessors().cardinality() > 1; + } + + /** + * See Appel algorithm 19.6: + * + * Place Phi functions in appropriate locations. + * + * @param ssaMeth {@code non-null;} method to process. + * Modifications are made in-place. + * @param localInfo {@code non-null;} local variable info, used + * when placing phis + * @param threshold registers below this number are ignored + */ + private static void placePhiFunctions (SsaMethod ssaMeth, + LocalVariableInfo localInfo, int threshold) { + ArrayList ssaBlocks; + int regCount; + int blockCount; + + ssaBlocks = ssaMeth.getBlocks(); + blockCount = ssaBlocks.size(); + regCount = ssaMeth.getRegCount() - threshold; + + DomFront df = new DomFront(ssaMeth); + DomFront.DomInfo[] domInfos = df.run(); + + // Bit set of registers vs block index "definition sites" + BitSet[] defsites = new BitSet[regCount]; + + // Bit set of registers vs block index "phi placement sites" + BitSet[] phisites = new BitSet[regCount]; + + for (int i = 0; i < regCount; i++) { + defsites[i] = new BitSet(blockCount); + phisites[i] = new BitSet(blockCount); + } + + /* + * For each register, build a set of all basic blocks where + * containing an assignment to that register. + */ + for (int bi = 0, s = ssaBlocks.size(); bi < s; bi++) { + SsaBasicBlock b = ssaBlocks.get(bi); + + for (SsaInsn insn : b.getInsns()) { + RegisterSpec rs = insn.getResult(); + + if (rs != null && rs.getReg() - threshold >= 0) { + defsites[rs.getReg() - threshold].set(bi); + } + } + } + + if (DEBUG) { + System.out.println("defsites"); + + for (int i = 0; i < regCount; i++) { + StringBuilder sb = new StringBuilder(); + sb.append('v').append(i).append(": "); + sb.append(defsites[i].toString()); + System.out.println(sb); + } + } + + BitSet worklist; + + /* + * For each register, compute all locations for phi placement + * based on dominance-frontier algorithm. + */ + for (int reg = 0, s = regCount; reg < s; reg++) { + int workBlockIndex; + + /* Worklist set starts out with each node where reg is assigned. */ + + worklist = (BitSet) (defsites[reg].clone()); + + while (0 <= (workBlockIndex = worklist.nextSetBit(0))) { + worklist.clear(workBlockIndex); + IntIterator dfIterator + = domInfos[workBlockIndex].dominanceFrontiers.iterator(); + + while (dfIterator.hasNext()) { + int dfBlockIndex = dfIterator.next(); + + if (!phisites[reg].get(dfBlockIndex)) { + phisites[reg].set(dfBlockIndex); + + int tReg = reg + threshold; + RegisterSpec rs + = localInfo.getStarts(dfBlockIndex).get(tReg); + + if (rs == null) { + ssaBlocks.get(dfBlockIndex).addPhiInsnForReg(tReg); + } else { + ssaBlocks.get(dfBlockIndex).addPhiInsnForReg(rs); + } + + if (!defsites[reg].get(dfBlockIndex)) { + worklist.set(dfBlockIndex); + } + } + } + } + } + + if (DEBUG) { + System.out.println("phisites"); + + for (int i = 0; i < regCount; i++) { + StringBuilder sb = new StringBuilder(); + sb.append('v').append(i).append(": "); + sb.append(phisites[i].toString()); + System.out.println(sb); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/SsaInsn.java b/dexlib/src/main/java/com/android/dx/ssa/SsaInsn.java new file mode 100644 index 000000000..fb82f2d61 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/SsaInsn.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.util.ToHuman; + +/** + * An instruction in SSA form + */ +public abstract class SsaInsn implements ToHuman, Cloneable { + /** {@code non-null;} the block that contains this instance */ + private final SsaBasicBlock block; + + /** {@code null-ok;} result register */ + private RegisterSpec result; + + /** + * Constructs an instance. + * + * @param result {@code null-ok;} initial result register. May be changed. + * @param block {@code non-null;} block containing this insn. Can + * never change. + */ + protected SsaInsn(RegisterSpec result, SsaBasicBlock block) { + if (block == null) { + throw new NullPointerException("block == null"); + } + + this.block = block; + this.result = result; + } + + /** + * Makes a new SSA insn form a rop insn. + * + * @param insn {@code non-null;} rop insn + * @param block {@code non-null;} owning block + * @return {@code non-null;} an appropriately constructed instance + */ + public static SsaInsn makeFromRop(Insn insn, SsaBasicBlock block) { + return new NormalSsaInsn(insn, block); + } + + /** {@inheritDoc} */ + @Override + public SsaInsn clone() { + try { + return (SsaInsn)super.clone(); + } catch (CloneNotSupportedException ex) { + throw new RuntimeException ("unexpected", ex); + } + } + + /** + * Like {@link com.android.dx.rop.code.Insn getResult()}. + * + * @return result register + */ + public RegisterSpec getResult() { + return result; + } + + /** + * Set the result register. + * + * @param result {@code non-null;} the new result register + */ + protected void setResult(RegisterSpec result) { + if (result == null) { + throw new NullPointerException("result == null"); + } + + this.result = result; + } + + /** + * Like {@link com.android.dx.rop.code.Insn getSources()}. + * + * @return {@code non-null;} sources list + */ + abstract public RegisterSpecList getSources(); + + /** + * Gets the block to which this insn instance belongs. + * + * @return owning block + */ + public SsaBasicBlock getBlock() { + return block; + } + + /** + * Returns whether or not the specified reg is the result reg. + * + * @param reg register to test + * @return true if there is a result and it is stored in the specified + * register + */ + public boolean isResultReg(int reg) { + return result != null && result.getReg() == reg; + } + + + /** + * Changes the result register if this insn has a result. This is used + * during renaming. + * + * @param reg new result register + */ + public void changeResultReg(int reg) { + if (result != null) { + result = result.withReg(reg); + } + } + + /** + * Sets the local association for the result of this insn. This is + * sometimes updated during the SsaRenamer process. + * + * @param local {@code null-ok;} new debug/local variable info + */ + public final void setResultLocal(LocalItem local) { + LocalItem oldItem = result.getLocalItem(); + + if (local != oldItem && (local == null + || !local.equals(result.getLocalItem()))) { + result = RegisterSpec.makeLocalOptional( + result.getReg(), result.getType(), local); + } + } + + /** + * Map registers after register allocation. + * + * @param mapper {@code non-null;} mapping from old to new registers + */ + public final void mapRegisters(RegisterMapper mapper) { + RegisterSpec oldResult = result; + + result = mapper.map(result); + block.getParent().updateOneDefinition(this, oldResult); + mapSourceRegisters(mapper); + } + + /** + * Maps only source registers. + * + * @param mapper new mapping + */ + abstract public void mapSourceRegisters(RegisterMapper mapper); + + /** + * Returns the Rop opcode for this insn, or null if this is a phi insn. + * + * TODO: Move this up into NormalSsaInsn. + * + * @return {@code null-ok;} Rop opcode if there is one. + */ + abstract public Rop getOpcode(); + + /** + * Returns the original Rop insn for this insn, or null if this is + * a phi insn. + * + * TODO: Move this up into NormalSsaInsn. + * + * @return {@code null-ok;} Rop insn if there is one. + */ + abstract public Insn getOriginalRopInsn(); + + /** + * Gets the spec of a local variable assignment that occurs at this + * instruction, or null if no local variable assignment occurs. This + * may be the result register, or for {@code mark-local} insns + * it may be the source. + * + * @see com.android.dx.rop.code.Insn#getLocalAssignment() + * + * @return {@code null-ok;} a local-associated register spec or null + */ + public RegisterSpec getLocalAssignment() { + if (result != null && result.getLocalItem() != null) { + return result; + } + + return null; + } + + /** + * Indicates whether the specified register is amongst the registers + * used as sources for this instruction. + * + * @param reg the register in question + * @return true if the reg is a source + */ + public boolean isRegASource(int reg) { + return null != getSources().specForRegister(reg); + } + + /** + * Transform back to ROP form. + * + * TODO: Move this up into NormalSsaInsn. + * + * @return {@code non-null;} a ROP representation of this instruction, with + * updated registers. + */ + public abstract Insn toRopInsn(); + + /** + * @return true if this is a PhiInsn or a normal move insn + */ + public abstract boolean isPhiOrMove(); + + /** + * Returns true if this insn is considered to have a side effect beyond + * that of assigning to the result reg. + * + * @return true if this insn is considered to have a side effect beyond + * that of assigning to the result reg. + */ + public abstract boolean hasSideEffect(); + + /** + * @return true if this is a move (but not a move-operand or + * move-exception) instruction + */ + public boolean isNormalMoveInsn() { + return false; + } + + /** + * @return true if this is a move-exception instruction. + * These instructions must immediately follow a preceeding invoke* + */ + public boolean isMoveException() { + return false; + } + + /** + * @return true if this instruction can throw. + */ + abstract public boolean canThrow(); + + /** + * Accepts a visitor. + * + * @param v {@code non-null} the visitor + */ + public abstract void accept(Visitor v); + + /** + * Visitor interface for this class. + */ + public static interface Visitor { + /** + * Any non-phi move instruction + * @param insn {@code non-null;} the instruction to visit + */ + public void visitMoveInsn(NormalSsaInsn insn); + + /** + * Any phi insn + * @param insn {@code non-null;} the instruction to visit + */ + public void visitPhiInsn(PhiInsn insn); + + /** + * Any insn that isn't a move or a phi (which is also a move). + * @param insn {@code non-null;} the instruction to visit + */ + public void visitNonMoveInsn(NormalSsaInsn insn); + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/SsaMethod.java b/dexlib/src/main/java/com/android/dx/ssa/SsaMethod.java new file mode 100644 index 000000000..7d7b565ca --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/SsaMethod.java @@ -0,0 +1,872 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.BasicBlockList; +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.util.IntList; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +/** + * A method in SSA form. + */ +public final class SsaMethod { + /** basic blocks, indexed by block index */ + private ArrayList blocks; + + /** Index of first executed block in method */ + private int entryBlockIndex; + + /** + * Index of exit block, which exists only in SSA form, + * or or {@code -1} if there is none + */ + private int exitBlockIndex; + + /** total number of registers required */ + private int registerCount; + + /** first register number to use for any temporary "spares" */ + private int spareRegisterBase; + + /** current count of spare registers used */ + private int borrowedSpareRegisters; + + /** really one greater than the max label */ + private int maxLabel; + + /** the total width, in register-units, of the method's parameters */ + private final int paramWidth; + + /** true if this method has no {@code this} pointer argument */ + private final boolean isStatic; + + /** + * indexed by register: the insn where said register is defined or null + * if undefined. null until (lazily) created. + */ + private SsaInsn[] definitionList; + + /** indexed by register: the list of all insns that use a register */ + private ArrayList[] useList; + + /** A version of useList with each List unmodifiable */ + private List[] unmodifiableUseList; + + /** + * "back-convert mode". Set during back-conversion when registers + * are about to be mapped into a non-SSA namespace. When true, + * use and def lists are unavailable. + * + * TODO: Remove this mode, and place the functionality elsewhere + */ + private boolean backMode; + + /** + * @param ropMethod rop-form method to convert from + * @param paramWidth the total width, in register-units, of the + * method's parameters + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + */ + public static SsaMethod newFromRopMethod(RopMethod ropMethod, + int paramWidth, boolean isStatic) { + SsaMethod result = new SsaMethod(ropMethod, paramWidth, isStatic); + + result.convertRopToSsaBlocks(ropMethod); + + return result; + } + + /** + * Constructs an instance. + * + * @param ropMethod {@code non-null;} the original rop-form method that + * this instance is based on + * @param paramWidth the total width, in register-units, of the + * method's parameters + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + */ + private SsaMethod(RopMethod ropMethod, int paramWidth, boolean isStatic) { + this.paramWidth = paramWidth; + this.isStatic = isStatic; + this.backMode = false; + this.maxLabel = ropMethod.getBlocks().getMaxLabel(); + this.registerCount = ropMethod.getBlocks().getRegCount(); + this.spareRegisterBase = registerCount; + } + + /** + * Builds a BitSet of block indices from a basic block list and a list + * of labels taken from Rop form. + * + * @param blocks Rop blocks + * @param labelList list of rop block labels + * @return BitSet of block indices + */ + static BitSet bitSetFromLabelList(BasicBlockList blocks, + IntList labelList) { + BitSet result = new BitSet(blocks.size()); + + for (int i = 0, sz = labelList.size(); i < sz; i++) { + result.set(blocks.indexOfLabel(labelList.get(i))); + } + + return result; + } + + /** + * Builds an IntList of block indices from a basic block list and a list + * of labels taken from Rop form. + * + * @param ropBlocks Rop blocks + * @param labelList list of rop block labels + * @return IntList of block indices + */ + public static IntList indexListFromLabelList(BasicBlockList ropBlocks, + IntList labelList) { + + IntList result = new IntList(labelList.size()); + + for (int i = 0, sz = labelList.size(); i < sz; i++) { + result.add(ropBlocks.indexOfLabel(labelList.get(i))); + } + + return result; + } + + private void convertRopToSsaBlocks(RopMethod rmeth) { + BasicBlockList ropBlocks = rmeth.getBlocks(); + int sz = ropBlocks.size(); + + blocks = new ArrayList(sz + 2); + + for (int i = 0; i < sz; i++) { + SsaBasicBlock sbb = SsaBasicBlock.newFromRop(rmeth, i, this); + blocks.add(sbb); + } + + // Add an no-op entry block. + int origEntryBlockIndex = rmeth.getBlocks() + .indexOfLabel(rmeth.getFirstLabel()); + + SsaBasicBlock entryBlock + = blocks.get(origEntryBlockIndex).insertNewPredecessor(); + + entryBlockIndex = entryBlock.getIndex(); + exitBlockIndex = -1; // This gets made later. + } + + /** + * Creates an exit block and attaches it to the CFG if this method + * exits. Methods that never exit will not have an exit block. This + * is called after edge-splitting and phi insertion, since the edges + * going into the exit block should not be considered in those steps. + */ + /*package*/ void makeExitBlock() { + if (exitBlockIndex >= 0) { + throw new RuntimeException("must be called at most once"); + } + + exitBlockIndex = blocks.size(); + SsaBasicBlock exitBlock + = new SsaBasicBlock(exitBlockIndex, maxLabel++, this); + + blocks.add(exitBlock); + + for (SsaBasicBlock block : blocks) { + block.exitBlockFixup(exitBlock); + } + + if (exitBlock.getPredecessors().cardinality() == 0) { + // In cases where there is no exit... + blocks.remove(exitBlockIndex); + exitBlockIndex = -1; + maxLabel--; + } + } + + /** + * Gets a new {@code GOTO} insn. + * + * @param block block to which this GOTO will be added + * (not it's destination!) + * @return an appropriately-constructed instance. + */ + private static SsaInsn getGoto(SsaBasicBlock block) { + return new NormalSsaInsn ( + new PlainInsn(Rops.GOTO, SourcePosition.NO_INFO, + null, RegisterSpecList.EMPTY), block); + } + + /** + * Makes a new basic block for this method, which is empty besides + * a single {@code GOTO}. Successors and predecessors are not yet + * set. + * + * @return new block + */ + public SsaBasicBlock makeNewGotoBlock() { + int newIndex = blocks.size(); + SsaBasicBlock newBlock = new SsaBasicBlock(newIndex, maxLabel++, this); + + newBlock.getInsns().add(getGoto(newBlock)); + blocks.add(newBlock); + + return newBlock; + } + + /** + * @return block index of first execution block + */ + public int getEntryBlockIndex() { + return entryBlockIndex; + } + + /** + * @return first execution block + */ + public SsaBasicBlock getEntryBlock() { + return blocks.get(entryBlockIndex); + } + + /** + * @return block index of exit block or {@code -1} if there is none + */ + public int getExitBlockIndex() { + return exitBlockIndex; + } + + /** + * @return {@code null-ok;} block of exit block or {@code null} if + * there is none + */ + public SsaBasicBlock getExitBlock() { + return exitBlockIndex < 0 ? null : blocks.get(exitBlockIndex); + } + + /** + * @param bi block index or {@code -1} for none + * @return rop label or {code -1} if {@code bi} was {@code -1} + */ + public int blockIndexToRopLabel(int bi) { + if (bi < 0) { + return -1; + } + return blocks.get(bi).getRopLabel(); + } + + /** + * @return count of registers used in this method + */ + public int getRegCount() { + return registerCount; + } + + /** + * @return the total width, in register units, of the method's + * parameters + */ + public int getParamWidth() { + return paramWidth; + } + + /** + * Returns {@code true} if this is a static method. + * + * @return {@code true} if this is a static method + */ + public boolean isStatic() { + return isStatic; + } + + /** + * Borrows a register to use as a temp. Used in the phi removal process. + * Call returnSpareRegisters() when done. + * + * @param category width (1 or 2) of the register + * @return register number to use + */ + public int borrowSpareRegister(int category) { + int result = spareRegisterBase + borrowedSpareRegisters; + + borrowedSpareRegisters += category; + registerCount = Math.max(registerCount, result + category); + + return result; + } + + /** + * Returns all borrowed registers. + */ + public void returnSpareRegisters() { + borrowedSpareRegisters = 0; + } + + /** + * @return {@code non-null;} basic block list. Do not modify. + */ + public ArrayList getBlocks() { + return blocks; + } + + /** + * Returns the count of reachable blocks in this method: blocks that have + * predecessors (or are the start block) + * + * @return {@code >= 0;} number of reachable basic blocks + */ + public int getCountReachableBlocks() { + int ret = 0; + + for (SsaBasicBlock b : blocks) { + // Blocks that have been disconnected don't count. + if (b.isReachable()) { + ret++; + } + } + + return ret; + } + + /** + * Computes reachability for all blocks in the method. First clears old + * values from all blocks, then starts with the entry block and walks down + * the control flow graph, marking all blocks it finds as reachable. + */ + public void computeReachability() { + for (SsaBasicBlock block : blocks) { + block.setReachable(0); + } + + ArrayList blockList = new ArrayList(); + blockList.add(this.getEntryBlock()); + + while (!blockList.isEmpty()) { + SsaBasicBlock block = blockList.remove(0); + if (block.isReachable()) continue; + + block.setReachable(1); + BitSet succs = block.getSuccessors(); + for (int i = succs.nextSetBit(0); i >= 0; + i = succs.nextSetBit(i + 1)) { + blockList.add(blocks.get(i)); + } + } + } + + /** + * Remaps unversioned registers. + * + * @param mapper maps old registers to new. + */ + public void mapRegisters(RegisterMapper mapper) { + for (SsaBasicBlock block : getBlocks()) { + for (SsaInsn insn : block.getInsns()) { + insn.mapRegisters(mapper); + } + } + + registerCount = mapper.getNewRegisterCount(); + spareRegisterBase = registerCount; + } + + /** + * Returns the insn that defines the given register + * @param reg register in question + * @return insn (actual instance from code) that defined this reg or null + * if reg is not defined. + */ + public SsaInsn getDefinitionForRegister(int reg) { + if (backMode) { + throw new RuntimeException("No def list in back mode"); + } + + if (definitionList != null) { + return definitionList[reg]; + } + + definitionList = new SsaInsn[getRegCount()]; + + forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn (NormalSsaInsn insn) { + definitionList[insn.getResult().getReg()] = insn; + } + public void visitPhiInsn (PhiInsn phi) { + definitionList[phi.getResult().getReg()] = phi; + } + public void visitNonMoveInsn (NormalSsaInsn insn) { + RegisterSpec result = insn.getResult(); + if (result != null) { + definitionList[insn.getResult().getReg()] = insn; + } + } + }); + + return definitionList[reg]; + } + + /** + * Builds useList and unmodifiableUseList. + */ + private void buildUseList() { + if (backMode) { + throw new RuntimeException("No use list in back mode"); + } + + useList = new ArrayList[registerCount]; + + for (int i = 0; i < registerCount; i++) { + useList[i] = new ArrayList(); + } + + forEachInsn(new SsaInsn.Visitor() { + /** {@inheritDoc} */ + public void visitMoveInsn (NormalSsaInsn insn) { + addToUses(insn); + } + /** {@inheritDoc} */ + public void visitPhiInsn (PhiInsn phi) { + addToUses(phi); + } + /** {@inheritDoc} */ + public void visitNonMoveInsn (NormalSsaInsn insn) { + addToUses(insn); + } + /** + * Adds specified insn to the uses list for all of its sources. + * @param insn {@code non-null;} insn to process + */ + private void addToUses(SsaInsn insn) { + RegisterSpecList rl = insn.getSources(); + int sz = rl.size(); + + for (int i = 0; i < sz; i++) { + useList[rl.get(i).getReg()].add(insn); + } + } + }); + + unmodifiableUseList = new List[registerCount]; + + for (int i = 0; i < registerCount; i++) { + unmodifiableUseList[i] = Collections.unmodifiableList(useList[i]); + } + } + + /** + * Updates the use list for a single change in source register. + * + * @param insn {@code non-null;} insn being changed + * @param oldSource {@code null-ok;} The source that was used, if + * applicable + * @param newSource {@code non-null;} the new source being used + */ + /*package*/ void onSourceChanged(SsaInsn insn, + RegisterSpec oldSource, RegisterSpec newSource) { + if (useList == null) return; + + if (oldSource != null) { + int reg = oldSource.getReg(); + useList[reg].remove(insn); + } + + int reg = newSource.getReg(); + if (useList.length <= reg) { + useList = null; + return; + } + useList[reg].add(insn); + } + + /** + * Updates the use list for a source list change. + * + * @param insn {@code insn non-null;} insn being changed. + * {@code insn.getSources()} must return the new source list. + * @param oldSources {@code null-ok;} list of sources that were + * previously used + */ + /*package*/ void onSourcesChanged(SsaInsn insn, + RegisterSpecList oldSources) { + if (useList == null) return; + + if (oldSources != null) { + removeFromUseList(insn, oldSources); + } + + RegisterSpecList sources = insn.getSources(); + int szNew = sources.size(); + + for (int i = 0; i < szNew; i++) { + int reg = sources.get(i).getReg(); + useList[reg].add(insn); + } + } + + /** + * Removes a given {@code insn} from the use lists for the given + * {@code oldSources} (rather than the sources currently + * returned by insn.getSources()). + * + * @param insn {@code non-null;} insn in question + * @param oldSources {@code null-ok;} registers whose use lists + * {@code insn} should be removed form + */ + private void removeFromUseList(SsaInsn insn, RegisterSpecList oldSources) { + if (oldSources == null) { + return; + } + + int szNew = oldSources.size(); + for (int i = 0; i < szNew; i++) { + if (!useList[oldSources.get(i).getReg()].remove(insn)) { + throw new RuntimeException("use not found"); + } + } + } + + /** + * Adds an insn to both the use and def lists. For use when adding + * a new insn to the method. + * + * @param insn {@code non-null;} insn to add + */ + /*package*/ void onInsnAdded(SsaInsn insn) { + onSourcesChanged(insn, null); + updateOneDefinition(insn, null); + } + + /** + * Removes an instruction from use and def lists. For use during + * instruction removal. + * + * @param insn {@code non-null;} insn to remove + */ + /*package*/ void onInsnRemoved(SsaInsn insn) { + if (useList != null) { + removeFromUseList(insn, insn.getSources()); + } + + RegisterSpec resultReg = insn.getResult(); + if (definitionList != null && resultReg != null) { + definitionList[resultReg.getReg()] = null; + } + } + + /** + * Indicates that the instruction list has changed or the SSA register + * count has increased, so that internal datastructures that rely on + * it should be rebuild. In general, the various other on* methods + * should be called in preference when changes occur if they are + * applicable. + */ + public void onInsnsChanged() { + // Definition list will need to be recomputed + definitionList = null; + + // Use list will need to be recomputed + useList = null; + unmodifiableUseList = null; + } + + /** + * Updates a single definition. + * + * @param insn {@code non-null;} insn who's result should be recorded as + * a definition + * @param oldResult {@code null-ok;} a previous result that should + * be no longer considered a definition by this insn + */ + /*package*/ void updateOneDefinition(SsaInsn insn, + RegisterSpec oldResult) { + if (definitionList == null) return; + + if (oldResult != null) { + int reg = oldResult.getReg(); + definitionList[reg] = null; + } + + RegisterSpec resultReg = insn.getResult(); + + if (resultReg != null) { + int reg = resultReg.getReg(); + + if (definitionList[reg] != null) { + throw new RuntimeException("Duplicate add of insn"); + } else { + definitionList[resultReg.getReg()] = insn; + } + } + } + + /** + * Returns the list of all source uses (not results) for a register. + * + * @param reg register in question + * @return unmodifiable instruction list + */ + public List getUseListForRegister(int reg) { + + if (unmodifiableUseList == null) { + buildUseList(); + } + + return unmodifiableUseList[reg]; + } + + /** + * Returns a modifiable copy of the register use list. + * + * @return modifiable copy of the use-list, indexed by register + */ + public ArrayList[] getUseListCopy() { + if (useList == null) { + buildUseList(); + } + + ArrayList[] useListCopy + = (ArrayList[])(new ArrayList[registerCount]); + + for (int i = 0; i < registerCount; i++) { + useListCopy[i] = (ArrayList)(new ArrayList(useList[i])); + } + + return useListCopy; + } + + /** + * Checks to see if the given SSA reg is ever associated with a local + * local variable. Each SSA reg may be associated with at most one + * local var. + * + * @param spec {@code non-null;} ssa reg + * @return true if reg is ever associated with a local + */ + public boolean isRegALocal(RegisterSpec spec) { + SsaInsn defn = getDefinitionForRegister(spec.getReg()); + + if (defn == null) { + // version 0 registers are never used as locals + return false; + } + + // Does the definition have a local associated with it? + if (defn.getLocalAssignment() != null) return true; + + // If not, is there a mark-local insn? + for (SsaInsn use : getUseListForRegister(spec.getReg())) { + Insn insn = use.getOriginalRopInsn(); + + if (insn != null + && insn.getOpcode().getOpcode() == RegOps.MARK_LOCAL) { + return true; + } + } + + return false; + } + + /** + * Sets the new register count after renaming. + * + * @param newRegCount new register count + */ + /*package*/ void setNewRegCount(int newRegCount) { + registerCount = newRegCount; + spareRegisterBase = registerCount; + onInsnsChanged(); + } + + /** + * Makes a new SSA register. For use after renaming has completed. + * + * @return {@code >=0;} new SSA register. + */ + public int makeNewSsaReg() { + int reg = registerCount++; + spareRegisterBase = registerCount; + onInsnsChanged(); + return reg; + } + + /** + * Visits all insns in this method. + * + * @param visitor {@code non-null;} callback interface + */ + public void forEachInsn(SsaInsn.Visitor visitor) { + for (SsaBasicBlock block : blocks) { + block.forEachInsn(visitor); + } + } + + /** + * Visits each phi insn in this method + * @param v {@code non-null;} callback. + * + */ + public void forEachPhiInsn(PhiInsn.Visitor v) { + for (SsaBasicBlock block : blocks) { + block.forEachPhiInsn(v); + } + } + + + /** + * Walks the basic block tree in depth-first order, calling the visitor + * method once for every block. This depth-first walk may be run forward + * from the method entry point or backwards from the method exit points. + * + * @param reverse true if this should walk backwards from the exit points + * @param v {@code non-null;} callback interface. {@code parent} is set + * unless this is the root node + */ + public void forEachBlockDepthFirst(boolean reverse, + SsaBasicBlock.Visitor v) { + BitSet visited = new BitSet(blocks.size()); + + // We push the parent first, then the child on the stack. + Stack stack = new Stack(); + + SsaBasicBlock rootBlock = reverse ? getExitBlock() : getEntryBlock(); + + if (rootBlock == null) { + // in the case there's no exit block + return; + } + + stack.add(null); // Start with null parent. + stack.add(rootBlock); + + while (stack.size() > 0) { + SsaBasicBlock cur = stack.pop(); + SsaBasicBlock parent = stack.pop(); + + if (!visited.get(cur.getIndex())) { + BitSet children + = reverse ? cur.getPredecessors() : cur.getSuccessors(); + for (int i = children.nextSetBit(0); i >= 0 + ; i = children.nextSetBit(i + 1)) { + stack.add(cur); + stack.add(blocks.get(i)); + } + visited.set(cur.getIndex()); + v.visitBlock(cur, parent); + } + } + } + + /** + * Visits blocks in dom-tree order, starting at the current node. + * The {@code parent} parameter of the Visitor.visitBlock callback + * is currently always set to null. + * + * @param v {@code non-null;} callback interface + */ + public void forEachBlockDepthFirstDom(SsaBasicBlock.Visitor v) { + BitSet visited = new BitSet(getBlocks().size()); + Stack stack = new Stack(); + + stack.add(getEntryBlock()); + + while (stack.size() > 0) { + SsaBasicBlock cur = stack.pop(); + ArrayList curDomChildren = cur.getDomChildren(); + + if (!visited.get(cur.getIndex())) { + // We walk the tree this way for historical reasons... + for (int i = curDomChildren.size() - 1; i >= 0; i--) { + SsaBasicBlock child = curDomChildren.get(i); + stack.add(child); + } + visited.set(cur.getIndex()); + v.visitBlock(cur, null); + } + } + } + + /** + * Deletes all insns in the set from this method. + * + * @param deletedInsns {@code non-null;} insns to delete + */ + public void deleteInsns(Set deletedInsns) { + for (SsaBasicBlock block : getBlocks()) { + ArrayList insns = block.getInsns(); + + for (int i = insns.size() - 1; i >= 0; i--) { + SsaInsn insn = insns.get(i); + + if (deletedInsns.contains(insn)) { + onInsnRemoved(insn); + insns.remove(i); + } + } + + // Check to see if we need to add a GOTO + + int insnsSz = insns.size(); + SsaInsn lastInsn = (insnsSz == 0) ? null : insns.get(insnsSz - 1); + + if (block != getExitBlock() && (insnsSz == 0 + || lastInsn.getOriginalRopInsn() == null + || lastInsn.getOriginalRopInsn().getOpcode() + .getBranchingness() == Rop.BRANCH_NONE)) { + // We managed to eat a throwable insn + + Insn gotoInsn = new PlainInsn(Rops.GOTO, + SourcePosition.NO_INFO, null, RegisterSpecList.EMPTY); + insns.add(SsaInsn.makeFromRop(gotoInsn, block)); + + // Remove secondary successors from this block + BitSet succs = block.getSuccessors(); + for (int i = succs.nextSetBit(0); i >= 0; + i = succs.nextSetBit(i + 1)) { + if (i != block.getPrimarySuccessorIndex()) { + block.removeSuccessor(i); + } + } + } + } + } + + /** + * Sets "back-convert mode". Set during back-conversion when registers + * are about to be mapped into a non-SSA namespace. When true, + * use and def lists are unavailable. + */ + public void setBackMode() { + backMode = true; + useList = null; + definitionList = null; + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/SsaRenamer.java b/dexlib/src/main/java/com/android/dx/ssa/SsaRenamer.java new file mode 100644 index 000000000..66391a05d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/SsaRenamer.java @@ -0,0 +1,661 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.type.Type; +import com.android.dx.util.IntList; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; + +/** + * Complete transformation to SSA form by renaming all registers accessed.

      + * + * See Appel algorithm 19.7

      + * + * Unlike the original algorithm presented in Appel, this renamer converts + * to a new flat (versionless) register space. The "version 0" registers, + * which represent the initial state of the Rop registers and should never + * actually be meaningfully accessed in a legal program, are represented + * as the first N registers in the SSA namespace. Subsequent assignments + * are assigned new unique names. Note that the incoming Rop representation + * has a concept of register widths, where 64-bit values are stored into + * two adjoining Rop registers. This adjoining register representation is + * ignored in SSA form conversion and while in SSA form, each register can be e + * either 32 or 64 bits wide depending on use. The adjoining-register + * represention is re-created later when converting back to Rop form.

      + * + * But, please note, the SSA Renamer's ignoring of the adjoining-register ROP + * representation means that unaligned accesses to 64-bit registers are not + * supported. For example, you cannot do a 32-bit operation on a portion of + * a 64-bit register. This will never be observed to happen when coming + * from Java code, of course.

      + * + * The implementation here, rather than keeping a single register version + * stack for the entire method as the dom tree is walked, instead keeps + * a mapping table for the current block being processed. Once the + * current block has been processed, this mapping table is then copied + * and used as the initial state for child blocks.

      + */ +public class SsaRenamer implements Runnable { + /** debug flag */ + private static final boolean DEBUG = false; + + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** next available SSA register */ + private int nextSsaReg; + + /** the number of original rop registers */ + private final int ropRegCount; + + /** work only on registers above this value */ + private int threshold; + + /** + * indexed by block index; register version state for each block start. + * This list is updated by each dom parent for its children. The only + * sub-arrays that exist at any one time are the start states for blocks + * yet to be processed by a {@code BlockRenamer} instance. + */ + private final RegisterSpec[][] startsForBlocks; + + /** map of SSA register number to debug (local var names) or null of n/a */ + private final ArrayList ssaRegToLocalItems; + + /** + * maps SSA registers back to the original rop number. Used for + * debug only. + */ + private IntList ssaRegToRopReg; + + /** + * Constructs an instance of the renamer + * + * @param ssaMeth {@code non-null;} un-renamed SSA method that will + * be renamed. + */ + public SsaRenamer(SsaMethod ssaMeth) { + ropRegCount = ssaMeth.getRegCount(); + + this.ssaMeth = ssaMeth; + + /* + * Reserve the first N registers in the SSA register space for + * "version 0" registers. + */ + nextSsaReg = ropRegCount; + threshold = 0; + startsForBlocks = new RegisterSpec[ssaMeth.getBlocks().size()][]; + + ssaRegToLocalItems = new ArrayList(); + + if (DEBUG) { + ssaRegToRopReg = new IntList(ropRegCount); + } + + /* + * Appel 19.7 + * + * Initialization: + * for each variable a // register i + * Count[a] <- 0 // nextSsaReg, flattened + * Stack[a] <- 0 // versionStack + * push 0 onto Stack[a] + * + */ + + // top entry for the version stack is version 0 + RegisterSpec[] initialRegMapping = new RegisterSpec[ropRegCount]; + for (int i = 0; i < ropRegCount; i++) { + // everyone starts with a version 0 register + initialRegMapping[i] = RegisterSpec.make(i, Type.VOID); + + if (DEBUG) { + ssaRegToRopReg.add(i); + } + } + + // Initial state for entry block + startsForBlocks[ssaMeth.getEntryBlockIndex()] = initialRegMapping; + } + + /** + * Constructs an instance of the renamer with threshold set + * + * @param ssaMeth {@code non-null;} un-renamed SSA method that will + * be renamed. + * @param thresh registers below this number are unchanged + */ + public SsaRenamer(SsaMethod ssaMeth, int thresh) { + this(ssaMeth); + threshold = thresh; + } + + /** + * Performs renaming transformation, modifying the method's instructions + * in-place. + */ + public void run() { + // Rename each block in dom-tree DFS order. + ssaMeth.forEachBlockDepthFirstDom(new SsaBasicBlock.Visitor() { + public void visitBlock (SsaBasicBlock block, + SsaBasicBlock unused) { + new BlockRenamer(block).process(); + } + }); + + ssaMeth.setNewRegCount(nextSsaReg); + ssaMeth.onInsnsChanged(); + + if (DEBUG) { + System.out.println("SSA\tRop"); + /* + * We're going to compute the version of the rop register + * by keeping a running total of how many times the rop + * register has been mapped. + */ + int[] versions = new int[ropRegCount]; + + int sz = ssaRegToRopReg.size(); + for (int i = 0; i < sz; i++) { + int ropReg = ssaRegToRopReg.get(i); + System.out.println(i + "\t" + ropReg + "[" + + versions[ropReg] + "]"); + versions[ropReg]++; + } + } + } + + /** + * Duplicates a RegisterSpec array. + * + * @param orig {@code non-null;} array to duplicate + * @return {@code non-null;} new instance + */ + private static RegisterSpec[] dupArray(RegisterSpec[] orig) { + RegisterSpec[] copy = new RegisterSpec[orig.length]; + + System.arraycopy(orig, 0, copy, 0, orig.length); + + return copy; + } + + /** + * Gets a local variable item for a specified register. + * + * @param ssaReg register in SSA name space + * @return {@code null-ok;} Local variable name or null if none + */ + private LocalItem getLocalForNewReg(int ssaReg) { + if (ssaReg < ssaRegToLocalItems.size()) { + return ssaRegToLocalItems.get(ssaReg); + } else { + return null; + } + } + + /** + * Records a debug (local variable) name for a specified register. + * + * @param ssaReg non-null named register spec in SSA name space + */ + private void setNameForSsaReg(RegisterSpec ssaReg) { + int reg = ssaReg.getReg(); + LocalItem local = ssaReg.getLocalItem(); + + ssaRegToLocalItems.ensureCapacity(reg + 1); + while (ssaRegToLocalItems.size() <= reg) { + ssaRegToLocalItems.add(null); + } + + ssaRegToLocalItems.set(reg, local); + } + + /** + * Returns true if this SSA register is below the specified threshold. + * Used when most code is already in SSA form, and renaming is needed only + * for registers above a certain threshold. + * + * @param ssaReg the SSA register in question + * @return {@code true} if its register number is below the threshold + */ + private boolean isBelowThresholdRegister(int ssaReg) { + return ssaReg < threshold; + } + + /** + * Returns true if this SSA register is a "version 0" + * register. All version 0 registers are assigned the first N register + * numbers, where N is the count of original rop registers. + * + * @param ssaReg the SSA register in question + * @return true if it is a version 0 register. + */ + private boolean isVersionZeroRegister(int ssaReg) { + return ssaReg < ropRegCount; + } + + /** + * Returns true if a and b are equal or are both null. + * + * @param a null-ok + * @param b null-ok + * @return Returns true if a and b are equal or are both null + */ + private static boolean equalsHandlesNulls(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Processes all insns in a block and renames their registers + * as appropriate. + */ + private class BlockRenamer implements SsaInsn.Visitor{ + /** {@code non-null;} block we're processing. */ + private final SsaBasicBlock block; + + /** + * {@code non-null;} indexed by old register name. The current + * top of the version stack as seen by this block. It's + * initialized from the ending state of its dom parent, + * updated as the block's instructions are processed, and then + * copied to each one of its dom children. + */ + private final RegisterSpec[] currentMapping; + + /** + * contains the set of moves we need to keep to preserve local + * var info. All other moves will be deleted. + */ + private final HashSet movesToKeep; + + /** + * maps the set of insns to replace after renaming is finished + * on the block. + */ + private final HashMap insnsToReplace; + + private final RenamingMapper mapper; + + /** + * Constructs a block renamer instance. Call {@code process} + * to process. + * + * @param block {@code non-null;} block to process + */ + BlockRenamer(final SsaBasicBlock block) { + this.block = block; + currentMapping = startsForBlocks[block.getIndex()]; + movesToKeep = new HashSet(); + insnsToReplace = new HashMap(); + mapper = new RenamingMapper(); + + // We don't need our own start state anymore + startsForBlocks[block.getIndex()] = null; + } + + /** + * Provides a register mapping between the old register space + * and the current renaming mapping. The mapping is updated + * as the current block's instructions are processed. + */ + private class RenamingMapper extends RegisterMapper { + public RenamingMapper() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public int getNewRegisterCount() { + return nextSsaReg; + } + + /** {@inheritDoc} */ + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec == null) return null; + + int reg = registerSpec.getReg(); + + // For debugging: assert that the mapped types are compatible. + if (DEBUG) { + RegisterSpec newVersion = currentMapping[reg]; + if (newVersion.getBasicType() != Type.BT_VOID + && registerSpec.getBasicFrameType() + != newVersion.getBasicFrameType()) { + + throw new RuntimeException( + "mapping registers of incompatible types! " + + registerSpec + + " " + currentMapping[reg]); + } + } + + return registerSpec.withReg(currentMapping[reg].getReg()); + } + } + + /** + * Renames all the variables in this block and inserts appriopriate + * phis in successor blocks. + */ + public void process() { + /* + * From Appel: + * + * Rename(n) = + * for each statement S in block n // 'statement' in 'block' + */ + + block.forEachInsn(this); + + updateSuccessorPhis(); + + // Delete all move insns in this block. + ArrayList insns = block.getInsns(); + int szInsns = insns.size(); + + for (int i = szInsns - 1; i >= 0 ; i--) { + SsaInsn insn = insns.get(i); + SsaInsn replaceInsn; + + replaceInsn = insnsToReplace.get(insn); + + if (replaceInsn != null) { + insns.set(i, replaceInsn); + } else if (insn.isNormalMoveInsn() + && !movesToKeep.contains(insn)) { + insns.remove(i); + } + } + + // Store the start states for our dom children. + boolean first = true; + for (SsaBasicBlock child : block.getDomChildren()) { + if (child != block) { + // Don't bother duplicating the array for the first child. + RegisterSpec[] childStart = first ? currentMapping + : dupArray(currentMapping); + + startsForBlocks[child.getIndex()] = childStart; + first = false; + } + } + + // currentMapping is owned by a child now. + } + + /** + * Enforces a few contraints when a register mapping is added. + * + *

        + *
      1. Ensures that all new SSA registers specs in the mapping + * table with the same register number are identical. In effect, once + * an SSA register spec has received or lost a local variable name, + * then every old-namespace register that maps to it should gain or + * lose its local variable name as well. + *
      2. Records the local name associated with the + * register so that a register is never associated with more than one + * local. + *
      3. ensures that only one SSA register + * at a time is considered to be associated with a local variable. When + * {@code currentMapping} is updated and the newly added element + * is named, strip that name from any other SSA registers. + *
      + * + * @param ropReg {@code >= 0;} rop register number + * @param ssaReg {@code non-null;} an SSA register that has just + * been added to {@code currentMapping} + */ + private void addMapping(int ropReg, RegisterSpec ssaReg) { + int ssaRegNum = ssaReg.getReg(); + LocalItem ssaRegLocal = ssaReg.getLocalItem(); + + currentMapping[ropReg] = ssaReg; + + /* + * Ensure all SSA register specs with the same reg are identical. + */ + for (int i = currentMapping.length - 1; i >= 0; i--) { + RegisterSpec cur = currentMapping[i]; + + if (ssaRegNum == cur.getReg()) { + currentMapping[i] = ssaReg; + } + } + + // All further steps are for registers with local information. + if (ssaRegLocal == null) { + return; + } + + // Record that this SSA reg has been associated with a local. + setNameForSsaReg(ssaReg); + + // Ensure that no other SSA regs are associated with this local. + for (int i = currentMapping.length - 1; i >= 0; i--) { + RegisterSpec cur = currentMapping[i]; + + if (ssaRegNum != cur.getReg() + && ssaRegLocal.equals(cur.getLocalItem())) { + currentMapping[i] = cur.withLocalItem(null); + } + } + } + + /** + * {@inheritDoc} + * + * Phi insns have their result registers renamed. + */ + public void visitPhiInsn(PhiInsn phi) { + /* don't process sources for phi's */ + processResultReg(phi); + } + + /** + * {@inheritDoc} + * + * Move insns are treated as a simple mapping operation, and + * will later be removed unless they represent a local variable + * assignment. If they represent a local variable assignement, they + * are preserved. + */ + public void visitMoveInsn(NormalSsaInsn insn) { + /* + * For moves: copy propogate the move if we can, but don't + * if we need to preserve local variable info and the + * result has a different name than the source. + */ + + RegisterSpec ropResult = insn.getResult(); + int ropResultReg = ropResult.getReg(); + int ropSourceReg = insn.getSources().get(0).getReg(); + + insn.mapSourceRegisters(mapper); + int ssaSourceReg = insn.getSources().get(0).getReg(); + + LocalItem sourceLocal + = currentMapping[ropSourceReg].getLocalItem(); + LocalItem resultLocal = ropResult.getLocalItem(); + + /* + * A move from a register that's currently associated with a local + * to one that will not be associated with a local does not need + * to be preserved, but the local association should remain. + * Hence, we inherit the sourceLocal where the resultLocal is null. + */ + + LocalItem newLocal + = (resultLocal == null) ? sourceLocal : resultLocal; + LocalItem associatedLocal = getLocalForNewReg(ssaSourceReg); + + /* + * If we take the new local, will only one local have ever + * been associated with this SSA reg? + */ + boolean onlyOneAssociatedLocal + = associatedLocal == null || newLocal == null + || newLocal.equals(associatedLocal); + + /* + * If we're going to copy-propogate, then the ssa register + * spec that's going to go into the mapping is made up of + * the source register number mapped from above, the type + * of the result, and the name either from the result (if + * specified) or inherited from the existing mapping. + * + * The move source has incomplete type information in null + * object cases, so the result type is used. + */ + RegisterSpec ssaReg + = RegisterSpec.makeLocalOptional( + ssaSourceReg, ropResult.getType(), newLocal); + + if (!Optimizer.getPreserveLocals() || (onlyOneAssociatedLocal + && equalsHandlesNulls(newLocal, sourceLocal)) && + threshold == 0) { + /* + * We don't have to keep this move to preserve local + * information. Either the name is the same, or the result + * register spec is unnamed. + */ + + addMapping(ropResultReg, ssaReg); + } else if (onlyOneAssociatedLocal && sourceLocal == null && + threshold == 0) { + /* + * The register was previously unnamed. This means that a + * local starts after it's first assignment in SSA form + */ + + RegisterSpecList ssaSources = RegisterSpecList.make( + RegisterSpec.make(ssaReg.getReg(), + ssaReg.getType(), newLocal)); + + SsaInsn newInsn + = SsaInsn.makeFromRop( + new PlainInsn(Rops.opMarkLocal(ssaReg), + SourcePosition.NO_INFO, null, ssaSources),block); + + insnsToReplace.put(insn, newInsn); + + // Just map as above. + addMapping(ropResultReg, ssaReg); + } else { + /* + * Do not copy-propogate, since the two registers have + * two different local-variable names. + */ + processResultReg(insn); + + movesToKeep.add(insn); + } + } + + /** + * {@inheritDoc} + * + * All insns that are not move or phi insns have their source registers + * mapped ot the current mapping. Their result registers are then + * renamed to a new SSA register which is then added to the current + * register mapping. + */ + public void visitNonMoveInsn(NormalSsaInsn insn) { + /* for each use of some variable X in S */ + insn.mapSourceRegisters(mapper); + + processResultReg(insn); + } + + /** + * Renames the result register of this insn and updates the + * current register mapping. Does nothing if this insn has no result. + * Applied to all non-move insns. + * + * @param insn insn to process. + */ + void processResultReg(SsaInsn insn) { + RegisterSpec ropResult = insn.getResult(); + + if (ropResult == null) { + return; + } + + int ropReg = ropResult.getReg(); + if (isBelowThresholdRegister(ropReg)) { + return; + } + + insn.changeResultReg(nextSsaReg); + addMapping(ropReg, insn.getResult()); + + if (DEBUG) { + ssaRegToRopReg.add(ropReg); + } + + nextSsaReg++; + } + + /** + * Updates the phi insns in successor blocks with operands based + * on the current mapping of the rop register the phis represent. + */ + private void updateSuccessorPhis() { + PhiInsn.Visitor visitor = new PhiInsn.Visitor() { + public void visitPhiInsn (PhiInsn insn) { + int ropReg; + + ropReg = insn.getRopResultReg(); + if (isBelowThresholdRegister(ropReg)) { + return; + } + + /* + * Never add a version 0 register as a phi + * operand. Version 0 registers represent the + * initial register state, and thus are never + * significant. Furthermore, the register liveness + * algorithm doesn't properly count them as "live + * in" at the beginning of the method. + */ + + RegisterSpec stackTop = currentMapping[ropReg]; + if (!isVersionZeroRegister(stackTop.getReg())) { + insn.addPhiOperand(stackTop, block); + } + } + }; + + BitSet successors = block.getSuccessors(); + for (int i = successors.nextSetBit(0); i >= 0; + i = successors.nextSetBit(i + 1)) { + SsaBasicBlock successor = ssaMeth.getBlocks().get(i); + successor.forEachPhiInsn(visitor); + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/back/FirstFitAllocator.java b/dexlib/src/main/java/com/android/dx/ssa/back/FirstFitAllocator.java new file mode 100644 index 000000000..a9043c283 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/back/FirstFitAllocator.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa.back; + +import com.android.dx.rop.code.CstInsn; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.ssa.BasicRegisterMapper; +import com.android.dx.ssa.NormalSsaInsn; +import com.android.dx.ssa.RegisterMapper; +import com.android.dx.ssa.SsaMethod; +import com.android.dx.util.BitIntSet; +import com.android.dx.util.IntSet; +import java.util.BitSet; + +/** + * Allocates registers via a naive n^2 register allocator. + * This allocator does not try to co-locate local variables or deal + * intelligently with different size register uses. + */ +public class FirstFitAllocator extends RegisterAllocator { + /** + * If true, allocator places parameters at the top of the frame + * in calling-convention order. + */ + private static final boolean PRESLOT_PARAMS = true; + + /** indexed by old reg; the set of old regs we've mapped */ + private final BitSet mapped; + + /** {@inheritDoc} */ + public FirstFitAllocator( + final SsaMethod ssaMeth, final InterferenceGraph interference) { + super(ssaMeth, interference); + + mapped = new BitSet(ssaMeth.getRegCount()); + } + + /** {@inheritDoc} */ + @Override + public boolean wantsParamsMovedHigh() { + return PRESLOT_PARAMS; + } + + /** {@inheritDoc} */ + @Override + public RegisterMapper allocateRegisters() { + int oldRegCount = ssaMeth.getRegCount(); + + BasicRegisterMapper mapper + = new BasicRegisterMapper(oldRegCount); + + int nextNewRegister = 0; + + if (PRESLOT_PARAMS) { + /* + * Reserve space for the params at the bottom of the register + * space. Later, we'll flip the params to the end of the register + * space. + */ + + nextNewRegister = ssaMeth.getParamWidth(); + } + + for (int i = 0; i < oldRegCount; i++) { + if (mapped.get(i)) { + // we already got this one + continue; + } + + int maxCategory = getCategoryForSsaReg(i); + IntSet current = new BitIntSet(oldRegCount); + + interference.mergeInterferenceSet(i, current); + + boolean isPreslotted = false; + int newReg = 0; + + if (PRESLOT_PARAMS && isDefinitionMoveParam(i)) { + // Any move-param definition must be a NormalSsaInsn + NormalSsaInsn defInsn = (NormalSsaInsn) + ssaMeth.getDefinitionForRegister(i); + + newReg = paramNumberFromMoveParam(defInsn); + + mapper.addMapping(i, newReg, maxCategory); + isPreslotted = true; + } else { + mapper.addMapping(i, nextNewRegister, maxCategory); + newReg = nextNewRegister; + } + + for (int j = i + 1; j < oldRegCount; j++) { + if (mapped.get(j) || isDefinitionMoveParam(j)) { + continue; + } + + /* + * If reg j doesn't interfere with the current mapping. + * Also, if this is a pre-slotted method parameter, we + * can't use more than the original param width. + */ + if (!current.has(j) + && !(isPreslotted + && (maxCategory < getCategoryForSsaReg(j)))) { + + interference.mergeInterferenceSet(j, current); + + maxCategory = Math.max(maxCategory, + getCategoryForSsaReg(j)); + + mapper.addMapping(j, newReg, maxCategory); + mapped.set(j); + } + } + + mapped.set(i); + if (!isPreslotted) { + nextNewRegister += maxCategory; + } + } + + return mapper; + } + + /** + * Returns the parameter number that this move-param insn refers to + * @param ndefInsn a move-param insn (otherwise, exceptions will be thrown) + * @return parameter number (offset in the total parameter width) + */ + private int paramNumberFromMoveParam(NormalSsaInsn ndefInsn) { + CstInsn origInsn = (CstInsn) ndefInsn.getOriginalRopInsn(); + + return ((CstInteger) origInsn.getConstant()).getValue(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/back/FirstFitLocalCombiningAllocator.java b/dexlib/src/main/java/com/android/dx/ssa/back/FirstFitLocalCombiningAllocator.java new file mode 100644 index 000000000..0fc532330 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/back/FirstFitLocalCombiningAllocator.java @@ -0,0 +1,1256 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa.back; + +import com.android.dx.dex.DexOptions; +import com.android.dx.rop.code.CstInsn; +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.ssa.InterferenceRegisterMapper; +import com.android.dx.ssa.NormalSsaInsn; +import com.android.dx.ssa.Optimizer; +import com.android.dx.ssa.PhiInsn; +import com.android.dx.ssa.RegisterMapper; +import com.android.dx.ssa.SsaBasicBlock; +import com.android.dx.ssa.SsaInsn; +import com.android.dx.ssa.SsaMethod; +import com.android.dx.util.IntIterator; +import com.android.dx.util.IntSet; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Map; +import java.util.TreeMap; + +/** + * Allocates registers in a first-fit fashion, with the bottom reserved for + * method parameters and all SSAregisters representing the same local variable + * kept together if possible. + */ +public class FirstFitLocalCombiningAllocator extends RegisterAllocator { + + /** + * Alignment constraint that can be used during search of free registers. + */ + private enum Alignment { + EVEN { + @Override + int nextClearBit(BitSet bitSet, int startIdx) { + int bitNumber = bitSet.nextClearBit(startIdx); + while (!isEven(bitNumber)) { + bitNumber = bitSet.nextClearBit(bitNumber + 1); + } + return bitNumber; + } + }, + ODD { + @Override + int nextClearBit(BitSet bitSet, int startIdx) { + int bitNumber = bitSet.nextClearBit(startIdx); + while (isEven(bitNumber)) { + bitNumber = bitSet.nextClearBit(bitNumber + 1); + } + return bitNumber; + } + }, + UNSPECIFIED { + @Override + int nextClearBit(BitSet bitSet, int startIdx) { + return bitSet.nextClearBit(startIdx); + } + }; + + /** + * Returns the index of the first bit that is set to {@code false} that occurs on or after the + * specified starting index and that respect {@link Alignment}. + * + * @param bitSet bitSet working on. + * @param startIdx {@code >= 0;} the index to start checking from (inclusive). + * @return the index of the next clear bit respecting alignment. + */ + abstract int nextClearBit(BitSet bitSet, int startIdx); + } + + /** local debug flag */ + private static final boolean DEBUG = false; + + /** maps local variable to a list of associated SSA registers */ + private final Map> localVariables; + + /** list of move-result-pesudo instructions seen in this method */ + private final ArrayList moveResultPseudoInsns; + + /** list of invoke-range instructions seen in this method */ + private final ArrayList invokeRangeInsns; + + /** list of phi instructions seen in this method */ + private final ArrayList phiInsns; + + /** indexed by SSA reg; the set of SSA regs we've mapped */ + private final BitSet ssaRegsMapped; + + /** Register mapper which will be our result */ + private final InterferenceRegisterMapper mapper; + + /** end of rop registers range (starting at 0) reserved for parameters */ + private final int paramRangeEnd; + + /** set of rop registers reserved for parameters or local variables */ + private final BitSet reservedRopRegs; + + /** set of rop registers that have been used by anything */ + private final BitSet usedRopRegs; + + /** true if converter should take steps to minimize rop-form registers */ + private final boolean minimizeRegisters; + + /** + * Constructs instance. + * + * @param ssaMeth {@code non-null;} method to process + * @param interference non-null interference graph for SSA registers + * @param minimizeRegisters true if converter should take steps to + * minimize rop-form registers + */ + public FirstFitLocalCombiningAllocator( + SsaMethod ssaMeth, InterferenceGraph interference, + boolean minimizeRegisters) { + super(ssaMeth, interference); + + ssaRegsMapped = new BitSet(ssaMeth.getRegCount()); + + mapper = new InterferenceRegisterMapper( + interference, ssaMeth.getRegCount()); + + this.minimizeRegisters = minimizeRegisters; + + /* + * Reserve space for the params at the bottom of the register + * space. Later, we'll flip the params to the end of the register + * space. + */ + + paramRangeEnd = ssaMeth.getParamWidth(); + + reservedRopRegs = new BitSet(paramRangeEnd * 2); + reservedRopRegs.set(0, paramRangeEnd); + usedRopRegs = new BitSet(paramRangeEnd * 2); + localVariables = new TreeMap>(); + moveResultPseudoInsns = new ArrayList(); + invokeRangeInsns = new ArrayList(); + phiInsns = new ArrayList(); + } + + /** {@inheritDoc} */ + @Override + public boolean wantsParamsMovedHigh() { + return true; + } + + /** {@inheritDoc} */ + @Override + public RegisterMapper allocateRegisters() { + + analyzeInstructions(); + + if (DEBUG) { + printLocalVars(); + } + + if (DEBUG) System.out.println("--->Mapping local-associated params"); + handleLocalAssociatedParams(); + + if (DEBUG) System.out.println("--->Mapping other params"); + handleUnassociatedParameters(); + + if (DEBUG) System.out.println("--->Mapping invoke-range"); + handleInvokeRangeInsns(); + + if (DEBUG) { + System.out.println("--->Mapping local-associated non-params"); + } + handleLocalAssociatedOther(); + + if (DEBUG) System.out.println("--->Mapping check-cast results"); + handleCheckCastResults(); + + if (DEBUG) System.out.println("--->Mapping phis"); + handlePhiInsns(); + + if (DEBUG) System.out.println("--->Mapping others"); + handleNormalUnassociated(); + + return mapper; + } + + /** + * Dumps local variable table to stdout for debugging. + */ + private void printLocalVars() { + System.out.println("Printing local vars"); + for (Map.Entry> e : + localVariables.entrySet()) { + StringBuilder regs = new StringBuilder(); + + regs.append('{'); + regs.append(' '); + for (RegisterSpec reg : e.getValue()) { + regs.append('v'); + regs.append(reg.getReg()); + regs.append(' '); + } + regs.append('}'); + System.out.printf("Local: %s Registers: %s\n", e.getKey(), regs); + } + } + + /** + * Maps all local-associated parameters to rop registers. + */ + private void handleLocalAssociatedParams() { + for (ArrayList ssaRegs : localVariables.values()) { + int sz = ssaRegs.size(); + int paramIndex = -1; + int paramCategory = 0; + + // First, find out if this local variable is a parameter. + for (int i = 0; i < sz; i++) { + RegisterSpec ssaSpec = ssaRegs.get(i); + int ssaReg = ssaSpec.getReg(); + + paramIndex = getParameterIndexForReg(ssaReg); + + if (paramIndex >= 0) { + paramCategory = ssaSpec.getCategory(); + addMapping(ssaSpec, paramIndex); + break; + } + } + + if (paramIndex < 0) { + // This local wasn't a parameter. + continue; + } + + // Any remaining local-associated registers will be mapped later. + tryMapRegs(ssaRegs, paramIndex, paramCategory, true); + } + } + + /** + * Gets the parameter index for SSA registers that are method parameters. + * {@code -1} is returned for non-parameter registers. + * + * @param ssaReg {@code >=0;} SSA register to look up + * @return parameter index or {@code -1} if not a parameter + */ + private int getParameterIndexForReg(int ssaReg) { + SsaInsn defInsn = ssaMeth.getDefinitionForRegister(ssaReg); + if (defInsn == null) { + return -1; + } + + Rop opcode = defInsn.getOpcode(); + + // opcode == null for phi insns. + if (opcode != null && opcode.getOpcode() == RegOps.MOVE_PARAM) { + CstInsn origInsn = (CstInsn) defInsn.getOriginalRopInsn(); + return ((CstInteger) origInsn.getConstant()).getValue(); + } + + return -1; + } + + /** + * Maps all local-associated registers that are not parameters. + * Tries to find an unreserved range that's wide enough for all of + * the SSA registers, and then tries to map them all to that + * range. If not all fit, a new range is tried until all registers + * have been fit. + */ + private void handleLocalAssociatedOther() { + for (ArrayList specs : localVariables.values()) { + int ropReg = paramRangeEnd; + + boolean done = false; + do { + int maxCategory = 1; + + // Compute max category for remaining unmapped registers. + int sz = specs.size(); + for (int i = 0; i < sz; i++) { + RegisterSpec ssaSpec = specs.get(i); + int category = ssaSpec.getCategory(); + if (!ssaRegsMapped.get(ssaSpec.getReg()) + && category > maxCategory) { + maxCategory = category; + } + } + + ropReg = findRopRegForLocal(ropReg, maxCategory); + if (canMapRegs(specs, ropReg)) { + done = tryMapRegs(specs, ropReg, maxCategory, true); + } + + // Increment for next call to findRopRegForLocal. + ropReg++; + } while (!done); + } + } + + /** + * Tries to map a list of SSA registers into the a rop reg, marking + * used rop space as reserved. SSA registers that don't fit are left + * unmapped. + * + * @param specs {@code non-null;} SSA registers to attempt to map + * @param ropReg {@code >=0;} rop register to map to + * @param maxAllowedCategory {@code 1..2;} maximum category + * allowed in mapping. + * @param markReserved do so if {@code true} + * @return {@code true} if all registers were mapped, {@code false} + * if some remain unmapped + */ + private boolean tryMapRegs( + ArrayList specs, int ropReg, + int maxAllowedCategory, boolean markReserved) { + boolean remaining = false; + for (RegisterSpec spec : specs) { + if (ssaRegsMapped.get(spec.getReg())) { + continue; + } + + boolean succeeded; + succeeded = tryMapReg(spec, ropReg, maxAllowedCategory); + remaining = !succeeded || remaining; + if (succeeded && markReserved) { + // This only needs to be called once really with + // the widest category used, but + markReserved(ropReg, spec.getCategory()); + } + } + return !remaining; + } + + /** + * Tries to map an SSA register to a rop register. + * + * @param ssaSpec {@code non-null;} SSA register + * @param ropReg {@code >=0;} rop register + * @param maxAllowedCategory {@code 1..2;} the maximum category + * that the SSA register is allowed to be + * @return {@code true} if map succeeded, {@code false} if not + */ + private boolean tryMapReg(RegisterSpec ssaSpec, int ropReg, + int maxAllowedCategory) { + if (ssaSpec.getCategory() <= maxAllowedCategory + && !ssaRegsMapped.get(ssaSpec.getReg()) + && canMapReg(ssaSpec, ropReg)) { + addMapping(ssaSpec, ropReg); + return true; + } + + return false; + } + + /** + * Marks a range of rop registers as "reserved for a local variable." + * + * @param ropReg {@code >= 0;} rop register to reserve + * @param category {@code > 0;} width to reserve + */ + private void markReserved(int ropReg, int category) { + reservedRopRegs.set(ropReg, ropReg + category, true); + } + + /** + * Checks to see if any rop registers in the specified range are reserved + * for local variables or parameters. + * + * @param ropRangeStart {@code >= 0;} lowest rop register + * @param width {@code > 0;} number of rop registers in range. + * @return {@code true} if any register in range is marked reserved + */ + private boolean rangeContainsReserved(int ropRangeStart, int width) { + for (int i = ropRangeStart; i < (ropRangeStart + width); i++) { + if (reservedRopRegs.get(i)) { + return true; + } + } + return false; + } + + /** + * Returns true if given rop register represents the {@code this} pointer + * for a non-static method. + * + * @param startReg rop register + * @return true if the "this" pointer is located here. + */ + private boolean isThisPointerReg(int startReg) { + // "this" is always the first parameter. + return startReg == 0 && !ssaMeth.isStatic(); + } + + /** + * Return the register alignment constraint to have 64-bits registers that will be align on even + * dalvik registers after that parameter registers are move up to the top of the frame to match + * the calling convention. + * + * @param regCategory category of the register that will be aligned. + * @return the register alignment constraint. + */ + private Alignment getAlignment(int regCategory) { + Alignment alignment = Alignment.UNSPECIFIED; + + if (DexOptions.ALIGN_64BIT_REGS_SUPPORT && regCategory == 2) { + if (isEven(paramRangeEnd)) { + alignment = Alignment.EVEN; + } else { + alignment = Alignment.ODD; + } + } + + return alignment; + } + + /** + * Finds unreserved rop registers with a specific category. + * + * @param startReg {@code >= 0;} a rop register to start the search at + * @param regCategory {@code > 0;} category of the searched registers. + * @return {@code >= 0;} start of available registers. + */ + private int findNextUnreservedRopReg(int startReg, int regCategory) { + return findNextUnreservedRopReg(startReg, regCategory, getAlignment(regCategory)); + } + + /** + * Finds a range of unreserved rop registers. + * + * @param startReg {@code >= 0;} a rop register to start the search at + * @param width {@code > 0;} the width, in registers, required. + * @param alignment the alignment constraint. + * @return {@code >= 0;} start of available register range. + */ + private int findNextUnreservedRopReg(int startReg, int width, Alignment alignment) { + int reg = alignment.nextClearBit(reservedRopRegs, startReg); + + while (true) { + int i = 1; + + while (i < width && !reservedRopRegs.get(reg + i)) { + i++; + } + + if (i == width) { + return reg; + } + + reg = alignment.nextClearBit(reservedRopRegs, reg + i); + } + } + + /** + * Finds rop registers that can be used for local variables. + * If {@code MIX_LOCALS_AND_OTHER} is {@code false}, this means any + * rop register that has not yet been used. + * + * @param startReg {@code >= 0;} a rop register to start the search at + * @param category {@code > 0;} the register category required. + * @return {@code >= 0;} start of available registers. + */ + private int findRopRegForLocal(int startReg, int category) { + Alignment alignment = getAlignment(category); + int reg = alignment.nextClearBit(usedRopRegs, startReg); + + while (true) { + int i = 1; + + while (i < category && !usedRopRegs.get(reg + i)) { + i++; + } + + if (i == category) { + return reg; + } + + reg = alignment.nextClearBit(usedRopRegs, reg + i); + } + } + + /** + * Maps any parameter that isn't local-associated, which can happen + * in the case where there is no java debug info. + */ + private void handleUnassociatedParameters() { + int szSsaRegs = ssaMeth.getRegCount(); + + for (int ssaReg = 0; ssaReg < szSsaRegs; ssaReg++) { + if (ssaRegsMapped.get(ssaReg)) { + // We already did this one above + continue; + } + + int paramIndex = getParameterIndexForReg(ssaReg); + + RegisterSpec ssaSpec = getDefinitionSpecForSsaReg(ssaReg); + if (paramIndex >= 0) { + addMapping(ssaSpec, paramIndex); + } + } + } + + /** + * Handles all insns that want a register range for their sources. + */ + private void handleInvokeRangeInsns() { + for (NormalSsaInsn insn : invokeRangeInsns) { + adjustAndMapSourceRangeRange(insn); + } + } + + /** + * Handles check cast results to reuse the same source register. + * Inserts a move if it can't map the same register to both and the + * check cast is not caught. + */ + private void handleCheckCastResults() { + for (NormalSsaInsn insn : moveResultPseudoInsns) { + RegisterSpec moveRegSpec = insn.getResult(); + int moveReg = moveRegSpec.getReg(); + BitSet predBlocks = insn.getBlock().getPredecessors(); + + // Expect one predecessor block only + if (predBlocks.cardinality() != 1) { + continue; + } + + SsaBasicBlock predBlock = + ssaMeth.getBlocks().get(predBlocks.nextSetBit(0)); + ArrayList insnList = predBlock.getInsns(); + + /** + * If the predecessor block has a check-cast, it will be the last + * instruction + */ + SsaInsn checkCastInsn = insnList.get(insnList.size() - 1); + if (checkCastInsn.getOpcode().getOpcode() != RegOps.CHECK_CAST) { + continue; + } + + RegisterSpec checkRegSpec = checkCastInsn.getSources().get(0); + int checkReg = checkRegSpec.getReg(); + + /** + * See if either register is already mapped. Most likely the move + * result will be mapped already since the cast result is stored + * in a local variable. + */ + int category = checkRegSpec.getCategory(); + boolean moveMapped = ssaRegsMapped.get(moveReg); + boolean checkMapped = ssaRegsMapped.get(checkReg); + if (moveMapped & !checkMapped) { + int moveRopReg = mapper.oldToNew(moveReg); + checkMapped = tryMapReg(checkRegSpec, moveRopReg, category); + } + if (checkMapped & !moveMapped) { + int checkRopReg = mapper.oldToNew(checkReg); + moveMapped = tryMapReg(moveRegSpec, checkRopReg, category); + } + + // Map any unmapped registers to anything available + if (!moveMapped || !checkMapped) { + int ropReg = findNextUnreservedRopReg(paramRangeEnd, category); + ArrayList ssaRegs = + new ArrayList(2); + ssaRegs.add(moveRegSpec); + ssaRegs.add(checkRegSpec); + + while (!tryMapRegs(ssaRegs, ropReg, category, false)) { + ropReg = findNextUnreservedRopReg(ropReg + 1, category); + } + } + + /* + * If source and result have a different mapping, insert a move so + * they can have the same mapping. Don't do this if the check cast + * is caught, since it will overwrite a potentially live value. + */ + boolean hasExceptionHandlers = + checkCastInsn.getOriginalRopInsn().getCatches().size() != 0; + int moveRopReg = mapper.oldToNew(moveReg); + int checkRopReg = mapper.oldToNew(checkReg); + if (moveRopReg != checkRopReg && !hasExceptionHandlers) { + ((NormalSsaInsn) checkCastInsn).changeOneSource(0, + insertMoveBefore(checkCastInsn, checkRegSpec)); + addMapping(checkCastInsn.getSources().get(0), moveRopReg); + } + } + } + + /** + * Handles all phi instructions, trying to map them to a common register. + */ + private void handlePhiInsns() { + for (PhiInsn insn : phiInsns) { + processPhiInsn(insn); + } + } + + /** + * Maps all non-parameter, non-local variable registers. + */ + private void handleNormalUnassociated() { + int szSsaRegs = ssaMeth.getRegCount(); + + for (int ssaReg = 0; ssaReg < szSsaRegs; ssaReg++) { + if (ssaRegsMapped.get(ssaReg)) { + // We already did this one + continue; + } + + RegisterSpec ssaSpec = getDefinitionSpecForSsaReg(ssaReg); + + if (ssaSpec == null) continue; + + int category = ssaSpec.getCategory(); + // Find a rop reg that does not interfere + int ropReg = findNextUnreservedRopReg(paramRangeEnd, category); + while (!canMapReg(ssaSpec, ropReg)) { + ropReg = findNextUnreservedRopReg(ropReg + 1, category); + } + + addMapping(ssaSpec, ropReg); + } + } + + /** + * Checks to see if a list of SSA registers can all be mapped into + * the same rop reg. Ignores registers that have already been mapped, + * and checks the interference graph and ensures the range does not + * cross the parameter range. + * + * @param specs {@code non-null;} SSA registers to check + * @param ropReg {@code >=0;} rop register to check mapping to + * @return {@code true} if all unmapped registers can be mapped + */ + private boolean canMapRegs(ArrayList specs, int ropReg) { + for (RegisterSpec spec : specs) { + if (ssaRegsMapped.get(spec.getReg())) continue; + if (!canMapReg(spec, ropReg)) return false; + } + return true; + } + + /** + * Checks to see if {@code ssaSpec} can be mapped to + * {@code ropReg}. Checks interference graph and ensures + * the range does not cross the parameter range. + * + * @param ssaSpec {@code non-null;} SSA spec + * @param ropReg prosepctive new-namespace reg + * @return {@code true} if mapping is possible + */ + private boolean canMapReg(RegisterSpec ssaSpec, int ropReg) { + int category = ssaSpec.getCategory(); + return !(spansParamRange(ropReg, category) + || mapper.interferes(ssaSpec, ropReg)); + } + + /** + * Returns true if the specified rop register + category + * will cross the boundry between the lower {@code paramWidth} + * registers reserved for method params and the upper registers. We cannot + * allocate a register that spans the param block and the normal block, + * because we will be moving the param block to high registers later. + * + * @param ssaReg register in new namespace + * @param category width that the register will have + * @return {@code true} in the case noted above + */ + private boolean spansParamRange(int ssaReg, int category) { + return ((ssaReg < paramRangeEnd) + && ((ssaReg + category) > paramRangeEnd)); + } + + /** + * Analyze each instruction and find out all the local variable assignments + * and move-result-pseudo/invoke-range instrucitons. + */ + private void analyzeInstructions() { + ssaMeth.forEachInsn(new SsaInsn.Visitor() { + /** {@inheritDoc} */ + public void visitMoveInsn(NormalSsaInsn insn) { + processInsn(insn); + } + + /** {@inheritDoc} */ + public void visitPhiInsn(PhiInsn insn) { + processInsn(insn); + } + + /** {@inheritDoc} */ + public void visitNonMoveInsn(NormalSsaInsn insn) { + processInsn(insn); + } + + /** + * This method collects three types of instructions: + * + * 1) Adds a local variable assignment to the + * {@code localVariables} map. + * 2) Add move-result-pseudo to the + * {@code moveResultPseudoInsns} list. + * 3) Add invoke-range to the + * {@code invokeRangeInsns} list. + * + * @param insn {@code non-null;} insn that may represent a + * local variable assignment + */ + private void processInsn(SsaInsn insn) { + RegisterSpec assignment; + assignment = insn.getLocalAssignment(); + + if (assignment != null) { + LocalItem local = assignment.getLocalItem(); + + ArrayList regList + = localVariables.get(local); + + if (regList == null) { + regList = new ArrayList(); + localVariables.put(local, regList); + } + + regList.add(assignment); + } + + if (insn instanceof NormalSsaInsn) { + if (insn.getOpcode().getOpcode() == + RegOps.MOVE_RESULT_PSEUDO) { + moveResultPseudoInsns.add((NormalSsaInsn) insn); + } else if (Optimizer.getAdvice().requiresSourcesInOrder( + insn.getOriginalRopInsn().getOpcode(), + insn.getSources())) { + invokeRangeInsns.add((NormalSsaInsn) insn); + } + } else if (insn instanceof PhiInsn) { + phiInsns.add((PhiInsn) insn); + } + + } + }); + } + + /** + * Adds a mapping from an SSA register to a rop register. + * {@link #canMapReg} should have already been called. + * + * @param ssaSpec {@code non-null;} SSA register to map from + * @param ropReg {@code >=0;} rop register to map to + */ + private void addMapping(RegisterSpec ssaSpec, int ropReg) { + int ssaReg = ssaSpec.getReg(); + + // An assertion. + if (ssaRegsMapped.get(ssaReg) || !canMapReg(ssaSpec, ropReg)) { + throw new RuntimeException( + "attempt to add invalid register mapping"); + } + + if (DEBUG) { + System.out.printf("Add mapping s%d -> v%d c:%d\n", + ssaSpec.getReg(), ropReg, ssaSpec.getCategory()); + } + + int category = ssaSpec.getCategory(); + mapper.addMapping(ssaSpec.getReg(), ropReg, category); + ssaRegsMapped.set(ssaReg); + usedRopRegs.set(ropReg, ropReg + category); + } + + + /** + * Maps the source registers of the specified instruction such that they + * will fall in a contiguous range in rop form. Moves are inserted as + * necessary to allow the range to be allocated. + * + * @param insn {@code non-null;} insn whos sources to process + */ + private void adjustAndMapSourceRangeRange(NormalSsaInsn insn) { + int newRegStart = findRangeAndAdjust(insn); + + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + int nextRopReg = newRegStart; + + for (int i = 0; i < szSources; i++) { + RegisterSpec source = sources.get(i); + int sourceReg = source.getReg(); + int category = source.getCategory(); + int curRopReg = nextRopReg; + nextRopReg += category; + + if (ssaRegsMapped.get(sourceReg)) { + continue; + } + + LocalItem localItem = getLocalItemForReg(sourceReg); + addMapping(source, curRopReg); + + if (localItem != null) { + markReserved(curRopReg, category); + ArrayList similarRegisters + = localVariables.get(localItem); + + int szSimilar = similarRegisters.size(); + + /* + * Try to map all SSA registers also associated with + * this local. + */ + for (int j = 0; j < szSimilar; j++) { + RegisterSpec similarSpec = similarRegisters.get(j); + int similarReg = similarSpec.getReg(); + + // Don't map anything that's also a source. + if (-1 != sources.indexOfRegister(similarReg)) { + continue; + } + + // Registers left unmapped will get handled later. + tryMapReg(similarSpec, curRopReg, category); + } + } + } + } + + /** + * Find a contiguous rop register range that fits the specified + * instruction's sources. First, try to center the range around + * sources that have already been mapped to rop registers. If that fails, + * just find a new contiguous range that doesn't interfere. + * + * @param insn {@code non-null;} the insn whose sources need to + * fit. Must be last insn in basic block. + * @return {@code >= 0;} rop register of start of range + */ + private int findRangeAndAdjust(NormalSsaInsn insn) { + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + // the category for each source index + int categoriesForIndex[] = new int[szSources]; + int rangeLength = 0; + + // Compute rangeLength and categoriesForIndex + for (int i = 0; i < szSources; i++) { + int category = sources.get(i).getCategory(); + categoriesForIndex[i] = category; + rangeLength += categoriesForIndex[i]; + } + + // the highest score of fits tried so far + int maxScore = Integer.MIN_VALUE; + // the high scoring range's start + int resultRangeStart = -1; + // by source index: set of sources needing moves in high scoring plan + BitSet resultMovesRequired = null; + + /* + * First, go through each source that's already been mapped. Try + * to center the range around the rop register this source is mapped + * to. + */ + int rangeStartOffset = 0; + for (int i = 0; i < szSources; i++) { + int ssaCenterReg = sources.get(i).getReg(); + + if (i != 0) { + rangeStartOffset -= categoriesForIndex[i - 1]; + } + if (!ssaRegsMapped.get(ssaCenterReg)) { + continue; + } + + int rangeStart = mapper.oldToNew(ssaCenterReg) + rangeStartOffset; + + if (rangeStart < 0 || spansParamRange(rangeStart, rangeLength)) { + continue; + } + + BitSet curMovesRequired = new BitSet(szSources); + + int fitWidth + = fitPlanForRange(rangeStart, insn, categoriesForIndex, + curMovesRequired); + + if (fitWidth < 0) { + continue; + } + + int score = fitWidth - curMovesRequired.cardinality(); + + if (score > maxScore) { + maxScore = score; + resultRangeStart = rangeStart; + resultMovesRequired = curMovesRequired; + } + + if (fitWidth == rangeLength) { + // We can't do any better than this, so stop here + break; + } + } + + /* + * If we were unable to find a plan for a fit centered around + * an already-mapped source, just try to find a range of + * registers we can move the range into. + */ + + if (resultRangeStart == -1) { + resultMovesRequired = new BitSet(szSources); + + resultRangeStart = findAnyFittingRange(insn, rangeLength, + categoriesForIndex, resultMovesRequired); + } + + /* + * Now, insert any moves required. + */ + + for (int i = resultMovesRequired.nextSetBit(0); i >= 0; + i = resultMovesRequired.nextSetBit(i+1)) { + insn.changeOneSource(i, insertMoveBefore(insn, sources.get(i))); + } + + return resultRangeStart; + } + + /** + * Finds an unreserved range that will fit the sources of the + * specified instruction. Does not bother trying to center the range + * around an already-mapped source register; + * + * @param insn {@code non-null;} insn to build range for + * @param rangeLength {@code >=0;} length required in register units + * @param categoriesForIndex {@code non-null;} indexed by source index; + * the category for each source + * @param outMovesRequired {@code non-null;} an output parameter indexed by + * source index that will contain the set of sources which need + * moves inserted + * @return the rop register that starts the fitting range + */ + private int findAnyFittingRange(NormalSsaInsn insn, int rangeLength, + int[] categoriesForIndex, BitSet outMovesRequired) { + Alignment alignment = Alignment.UNSPECIFIED; + + if (DexOptions.ALIGN_64BIT_REGS_SUPPORT) { + int regNumber = 0; + int p64bitsAligned = 0; + int p64bitsNotAligned = 0; + for (int category : categoriesForIndex) { + if (category == 2) { + if (isEven(regNumber)) { + p64bitsAligned++; + } else { + p64bitsNotAligned++; + } + regNumber += 2; + } else { + regNumber += 1; + } + } + + if (p64bitsNotAligned > p64bitsAligned) { + if (isEven(paramRangeEnd)) { + alignment = Alignment.ODD; + } else { + alignment = Alignment.EVEN; + } + } else if (p64bitsAligned > 0) { + if (isEven(paramRangeEnd)) { + alignment = Alignment.EVEN; + } else { + alignment = Alignment.ODD; + } + } + } + + int rangeStart = paramRangeEnd; + while (true) { + rangeStart = findNextUnreservedRopReg(rangeStart, rangeLength, alignment); + + int fitWidth = fitPlanForRange(rangeStart, insn, categoriesForIndex, outMovesRequired); + + if (fitWidth >= 0) { + break; + } + rangeStart++; + outMovesRequired.clear(); + } + + return rangeStart; + } + + /** + * Attempts to build a plan for fitting a range of sources into rop + * registers. + * + * @param ropReg {@code >= 0;} rop reg that begins range + * @param insn {@code non-null;} insn to plan range for + * @param categoriesForIndex {@code non-null;} indexed by source index; + * the category for each source + * @param outMovesRequired {@code non-null;} an output parameter indexed by + * source index that will contain the set of sources which need + * moves inserted + * @return the width of the fit that that does not involve added moves or + * {@code -1} if "no fit possible" + */ + private int fitPlanForRange(int ropReg, NormalSsaInsn insn, + int[] categoriesForIndex, BitSet outMovesRequired) { + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + int fitWidth = 0; + IntSet liveOut = insn.getBlock().getLiveOutRegs(); + RegisterSpecList liveOutSpecs = ssaSetToSpecs(liveOut); + + // An SSA reg may only be mapped into a range once. + BitSet seen = new BitSet(ssaMeth.getRegCount()); + + for (int i = 0; i < szSources ; i++) { + RegisterSpec ssaSpec = sources.get(i); + int ssaReg = ssaSpec.getReg(); + int category = categoriesForIndex[i]; + + if (i != 0) { + ropReg += categoriesForIndex[i-1]; + } + + if (ssaRegsMapped.get(ssaReg) + && mapper.oldToNew(ssaReg) == ropReg) { + // This is a register that is already mapped appropriately. + fitWidth += category; + } else if (rangeContainsReserved(ropReg, category)) { + fitWidth = -1; + break; + } else if (!ssaRegsMapped.get(ssaReg) + && canMapReg(ssaSpec, ropReg) + && !seen.get(ssaReg)) { + // This is a register that can be mapped appropriately. + fitWidth += category; + } else if (!mapper.areAnyPinned(liveOutSpecs, ropReg, category) + && !mapper.areAnyPinned(sources, ropReg, category)) { + /* + * This is a source that can be moved. We can insert a + * move as long as: + * + * * no SSA register pinned to the desired rop reg + * is live out on the block + * + * * no SSA register pinned to desired rop reg is + * a source of this insn (since this may require + * overlapping moves, which we can't presently handle) + */ + + outMovesRequired.set(i); + } else { + fitWidth = -1; + break; + } + + seen.set(ssaReg); + } + return fitWidth; + } + + /** + * Converts a bit set of SSA registers into a RegisterSpecList containing + * the definition specs of all the registers. + * + * @param ssaSet {@code non-null;} set of SSA registers + * @return list of RegisterSpecs as noted above + */ + RegisterSpecList ssaSetToSpecs(IntSet ssaSet) { + RegisterSpecList result = new RegisterSpecList(ssaSet.elements()); + + IntIterator iter = ssaSet.iterator(); + + int i = 0; + while (iter.hasNext()) { + result.set(i++, getDefinitionSpecForSsaReg(iter.next())); + } + + return result; + } + + /** + * Gets a local item associated with an ssa register, if one exists. + * + * @param ssaReg {@code >= 0;} SSA register + * @return {@code null-ok;} associated local item or null + */ + private LocalItem getLocalItemForReg(int ssaReg) { + for (Map.Entry> entry : + localVariables.entrySet()) { + for (RegisterSpec spec : entry.getValue()) { + if (spec.getReg() == ssaReg) { + return entry.getKey(); + } + } + } + + return null; + } + + /** + * Attempts to map the sources and result of a phi to a common register. + * Will try existing mappings first, from most to least common. If none + * of the registers have mappings yet, a new mapping is created. + */ + private void processPhiInsn(PhiInsn insn) { + RegisterSpec result = insn.getResult(); + int resultReg = result.getReg(); + int category = result.getCategory(); + + RegisterSpecList sources = insn.getSources(); + int sourcesSize = sources.size(); + + // List of phi sources / result that need mapping + ArrayList ssaRegs = new ArrayList(); + + // Track how many times a particular mapping is found + Multiset mapSet = new Multiset(sourcesSize + 1); + + /* + * If the result of the phi has an existing mapping, get it. + * Otherwise, add it to the list of regs that need mapping. + */ + if (ssaRegsMapped.get(resultReg)) { + mapSet.add(mapper.oldToNew(resultReg)); + } else { + ssaRegs.add(result); + } + + for (int i = 0; i < sourcesSize; i++) { + RegisterSpec source = sources.get(i); + SsaInsn def = ssaMeth.getDefinitionForRegister(source.getReg()); + RegisterSpec sourceDef = def.getResult(); + int sourceReg = sourceDef.getReg(); + + /* + * If a source of the phi has an existing mapping, get it. + * Otherwise, add it to the list of regs that need mapping. + */ + if (ssaRegsMapped.get(sourceReg)) { + mapSet.add(mapper.oldToNew(sourceReg)); + } else { + ssaRegs.add(sourceDef); + } + } + + // Try all existing mappings, with the most common ones first + for (int i = 0; i < mapSet.getSize(); i++) { + int maxReg = mapSet.getAndRemoveHighestCount(); + tryMapRegs(ssaRegs, maxReg, category, false); + } + + // Map any remaining unmapped regs with whatever fits + int mapReg = findNextUnreservedRopReg(paramRangeEnd, category); + while (!tryMapRegs(ssaRegs, mapReg, category, false)) { + mapReg = findNextUnreservedRopReg(mapReg + 1, category); + } + } + + private static boolean isEven(int regNumger) { + return ((regNumger & 1) == 0); + } + + // A set that tracks how often elements are added to it. + private static class Multiset { + private final int[] reg; + private final int[] count; + private int size; + + /** + * Constructs an instance. + * + * @param maxSize the maximum distinct elements the set may have + */ + public Multiset(int maxSize) { + reg = new int[maxSize]; + count = new int[maxSize]; + size = 0; + } + + /** + * Adds an element to the set. + * + * @param element element to add + */ + public void add(int element) { + for (int i = 0; i < size; i++) { + if (reg[i] == element) { + count[i]++; + return; + } + } + + reg[size] = element; + count[size] = 1; + size++; + } + + /** + * Searches the set for the element that has been added the most. + * In the case of a tie, the element that was added first is returned. + * Then, it clears the count on that element. The size of the set + * remains unchanged. + * + * @return element with the highest count + */ + public int getAndRemoveHighestCount() { + int maxIndex = -1; + int maxReg = -1; + int maxCount = 0; + + for (int i = 0; i < size; i++) { + if (maxCount < count[i]) { + maxIndex = i; + maxReg = reg[i]; + maxCount = count[i]; + } + } + + count[maxIndex] = 0; + return maxReg; + } + + /** + * Gets the number of distinct elements in the set. + * + * @return size of the set + */ + public int getSize() { + return size; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/back/IdenticalBlockCombiner.java b/dexlib/src/main/java/com/android/dx/ssa/back/IdenticalBlockCombiner.java new file mode 100644 index 000000000..b2be1dd0c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/back/IdenticalBlockCombiner.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa.back; + +import com.android.dx.rop.code.BasicBlock; +import com.android.dx.rop.code.BasicBlockList; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.util.IntList; +import java.util.BitSet; + +/** + * Searches for basic blocks that all have the same successor and insns + * but different predecessors. These blocks are then combined into a single + * block and the now-unused blocks are deleted. These identical blocks + * frequently are created when catch blocks are edge-split. + */ +public class IdenticalBlockCombiner { + private final RopMethod ropMethod; + private final BasicBlockList blocks; + private final BasicBlockList newBlocks; + + /** + * Constructs instance. Call {@code process()} to run. + * + * @param rm {@code non-null;} instance to process + */ + public IdenticalBlockCombiner(RopMethod rm) { + ropMethod = rm; + blocks = ropMethod.getBlocks(); + newBlocks = blocks.getMutableCopy(); + } + + /** + * Runs algorithm. TODO: This is n^2, and could be made linear-ish with + * a hash. In particular, hash the contents of each block and only + * compare blocks with the same hash. + * + * @return {@code non-null;} new method that has been processed + */ + public RopMethod process() { + int szBlocks = blocks.size(); + // indexed by label + BitSet toDelete = new BitSet(blocks.getMaxLabel()); + + // For each non-deleted block... + for (int bindex = 0; bindex < szBlocks; bindex++) { + BasicBlock b = blocks.get(bindex); + + if (toDelete.get(b.getLabel())) { + // doomed block + continue; + } + + IntList preds = ropMethod.labelToPredecessors(b.getLabel()); + + // ...look at all of it's predecessors that have only one succ... + int szPreds = preds.size(); + for (int i = 0; i < szPreds; i++) { + int iLabel = preds.get(i); + + BasicBlock iBlock = blocks.labelToBlock(iLabel); + + if (toDelete.get(iLabel) + || iBlock.getSuccessors().size() > 1 + || iBlock.getFirstInsn().getOpcode().getOpcode() == + RegOps.MOVE_RESULT) { + continue; + } + + IntList toCombine = new IntList(); + + // ...and see if they can be combined with any other preds... + for (int j = i + 1; j < szPreds; j++) { + int jLabel = preds.get(j); + BasicBlock jBlock = blocks.labelToBlock(jLabel); + + if (jBlock.getSuccessors().size() == 1 + && compareInsns(iBlock, jBlock)) { + + toCombine.add(jLabel); + toDelete.set(jLabel); + } + } + + combineBlocks(iLabel, toCombine); + } + } + + for (int i = szBlocks - 1; i >= 0; i--) { + if (toDelete.get(newBlocks.get(i).getLabel())) { + newBlocks.set(i, null); + } + } + + newBlocks.shrinkToFit(); + newBlocks.setImmutable(); + + return new RopMethod(newBlocks, ropMethod.getFirstLabel()); + } + + /** + * Helper method to compare the contents of two blocks. + * + * @param a {@code non-null;} a block to compare + * @param b {@code non-null;} another block to compare + * @return {@code true} iff the two blocks' instructions are the same + */ + private static boolean compareInsns(BasicBlock a, BasicBlock b) { + return a.getInsns().contentEquals(b.getInsns()); + } + + /** + * Combines blocks proven identical into one alpha block, re-writing + * all of the successor links that point to the beta blocks to point + * to the alpha block instead. + * + * @param alphaLabel block that will replace all the beta block + * @param betaLabels label list of blocks to combine + */ + private void combineBlocks(int alphaLabel, IntList betaLabels) { + int szBetas = betaLabels.size(); + + for (int i = 0; i < szBetas; i++) { + int betaLabel = betaLabels.get(i); + BasicBlock bb = blocks.labelToBlock(betaLabel); + IntList preds = ropMethod.labelToPredecessors(bb.getLabel()); + int szPreds = preds.size(); + + for (int j = 0; j < szPreds; j++) { + BasicBlock predBlock = newBlocks.labelToBlock(preds.get(j)); + replaceSucc(predBlock, betaLabel, alphaLabel); + } + } + } + + /** + * Replaces one of a block's successors with a different label. Constructs + * an updated BasicBlock instance and places it in {@code newBlocks}. + * + * @param block block to replace + * @param oldLabel label of successor to replace + * @param newLabel label of new successor + */ + private void replaceSucc(BasicBlock block, int oldLabel, int newLabel) { + IntList newSuccessors = block.getSuccessors().mutableCopy(); + int newPrimarySuccessor; + + newSuccessors.set(newSuccessors.indexOf(oldLabel), newLabel); + newPrimarySuccessor = block.getPrimarySuccessor(); + + if (newPrimarySuccessor == oldLabel) { + newPrimarySuccessor = newLabel; + } + + newSuccessors.setImmutable(); + + BasicBlock newBB = new BasicBlock(block.getLabel(), + block.getInsns(), newSuccessors, newPrimarySuccessor); + + newBlocks.set(newBlocks.indexOfLabel(block.getLabel()), newBB); + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/back/InterferenceGraph.java b/dexlib/src/main/java/com/android/dx/ssa/back/InterferenceGraph.java new file mode 100644 index 000000000..5ed2f820b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/back/InterferenceGraph.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa.back; + +import com.android.dx.ssa.SetFactory; +import com.android.dx.util.IntSet; +import java.util.ArrayList; + +/** + * A register interference graph + */ +public class InterferenceGraph { + /** + * {@code non-null;} interference graph, indexed by register in + * both dimensions + */ + private final ArrayList interference; + + /** + * Creates a new graph. + * + * @param countRegs {@code >= 0;} the start count of registers in + * the namespace. New registers can be added subsequently. + */ + public InterferenceGraph(int countRegs) { + interference = new ArrayList(countRegs); + + for (int i = 0; i < countRegs; i++) { + interference.add(SetFactory.makeInterferenceSet(countRegs)); + } + } + + /** + * Adds a register pair to the interference/liveness graph. Parameter + * order is insignificant. + * + * @param regV one register index + * @param regW another register index + */ + public void add(int regV, int regW) { + ensureCapacity(Math.max(regV, regW) + 1); + + interference.get(regV).add(regW); + interference.get(regW).add(regV); + } + + /** + * Dumps interference graph to stdout for debugging. + */ + public void dumpToStdout() { + int oldRegCount = interference.size(); + + for (int i = 0; i < oldRegCount; i++) { + StringBuilder sb = new StringBuilder(); + + sb.append("Reg " + i + ":" + interference.get(i).toString()); + + System.out.println(sb.toString()); + } + } + + /** + * Merges the interference set for a register into a given bit set + * + * @param reg {@code >= 0;} register + * @param set {@code non-null;} interference set; will be merged + * with set for given register + */ + public void mergeInterferenceSet(int reg, IntSet set) { + if (reg < interference.size()) { + set.merge(interference.get(reg)); + } + } + + /** + * Ensures that the interference graph is appropriately sized. + * + * @param size requested minumum size + */ + private void ensureCapacity(int size) { + int countRegs = interference.size(); + + interference.ensureCapacity(size); + + for (int i = countRegs; i < size; i++) { + interference.add(SetFactory.makeInterferenceSet(size)); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/back/LivenessAnalyzer.java b/dexlib/src/main/java/com/android/dx/ssa/back/LivenessAnalyzer.java new file mode 100644 index 000000000..0ea195af9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/back/LivenessAnalyzer.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa.back; + +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.ssa.PhiInsn; +import com.android.dx.ssa.SsaBasicBlock; +import com.android.dx.ssa.SsaInsn; +import com.android.dx.ssa.SsaMethod; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +/** + * From Appel "Modern Compiler Implementation in Java" algorithm 19.17 + * Calculate the live ranges for register {@code reg}.

      + * + * v = regV

      + * s = insn

      + * M = visitedBlocks

      + */ +public class LivenessAnalyzer { + /** + * {@code non-null;} index by basic block indexed set of basic blocks + * that have already been visited. "M" as written in the original Appel + * algorithm. + */ + private final BitSet visitedBlocks; + + /** + * {@code non-null;} set of blocks remaing to visit as "live out as block" + */ + private final BitSet liveOutBlocks; + + /** + * {@code >=0;} SSA register currently being analyzed. + * "v" in the original Appel algorithm. + */ + private final int regV; + + /** method to process */ + private final SsaMethod ssaMeth; + + /** interference graph being updated */ + private final InterferenceGraph interference; + + /** block "n" in Appel 19.17 */ + private SsaBasicBlock blockN; + + /** index of statement {@code s} in {@code blockN} */ + private int statementIndex; + + /** the next function to call */ + private NextFunction nextFunction; + + /** constants for {@link #nextFunction} */ + private static enum NextFunction { + LIVE_IN_AT_STATEMENT, + LIVE_OUT_AT_STATEMENT, + LIVE_OUT_AT_BLOCK, + DONE; + } + + /** + * Runs register liveness algorithm for a method, updating the + * live in/out information in {@code SsaBasicBlock} instances and + * returning an interference graph. + * + * @param ssaMeth {@code non-null;} method to process + * @return {@code non-null;} interference graph indexed by SSA + * registers in both directions + */ + public static InterferenceGraph constructInterferenceGraph( + SsaMethod ssaMeth) { + int szRegs = ssaMeth.getRegCount(); + InterferenceGraph interference = new InterferenceGraph(szRegs); + + for (int i = 0; i < szRegs; i++) { + new LivenessAnalyzer(ssaMeth, i, interference).run(); + } + + coInterferePhis(ssaMeth, interference); + + return interference; + } + + /** + * Makes liveness analyzer instance for specific register. + * + * @param ssaMeth {@code non-null;} method to process + * @param reg register whose liveness to analyze + * @param interference {@code non-null;} indexed by SSA reg in + * both dimensions; graph to update + * + */ + private LivenessAnalyzer(SsaMethod ssaMeth, int reg, + InterferenceGraph interference) { + int blocksSz = ssaMeth.getBlocks().size(); + + this.ssaMeth = ssaMeth; + this.regV = reg; + visitedBlocks = new BitSet(blocksSz); + liveOutBlocks = new BitSet(blocksSz); + this.interference = interference; + } + + /** + * The algorithm in Appel is presented in partial tail-recursion + * form. Obviously, that's not efficient in java, so this function + * serves as the dispatcher instead. + */ + private void handleTailRecursion() { + while (nextFunction != NextFunction.DONE) { + switch (nextFunction) { + case LIVE_IN_AT_STATEMENT: + nextFunction = NextFunction.DONE; + liveInAtStatement(); + break; + + case LIVE_OUT_AT_STATEMENT: + nextFunction = NextFunction.DONE; + liveOutAtStatement(); + break; + + case LIVE_OUT_AT_BLOCK: + nextFunction = NextFunction.DONE; + liveOutAtBlock(); + break; + + default: + } + } + } + + /** + * From Appel algorithm 19.17. + */ + public void run() { + List useList = ssaMeth.getUseListForRegister(regV); + + for (SsaInsn insn : useList) { + nextFunction = NextFunction.DONE; + + if (insn instanceof PhiInsn) { + // If s is a phi-function with V as it's ith argument. + PhiInsn phi = (PhiInsn) insn; + + for (SsaBasicBlock pred : + phi.predBlocksForReg(regV, ssaMeth)) { + blockN = pred; + + nextFunction = NextFunction.LIVE_OUT_AT_BLOCK; + handleTailRecursion(); + } + } else { + blockN = insn.getBlock(); + statementIndex = blockN.getInsns().indexOf(insn); + + if (statementIndex < 0) { + throw new RuntimeException( + "insn not found in it's own block"); + } + + nextFunction = NextFunction.LIVE_IN_AT_STATEMENT; + handleTailRecursion(); + } + } + + int nextLiveOutBlock; + while ((nextLiveOutBlock = liveOutBlocks.nextSetBit(0)) >= 0) { + blockN = ssaMeth.getBlocks().get(nextLiveOutBlock); + liveOutBlocks.clear(nextLiveOutBlock); + nextFunction = NextFunction.LIVE_OUT_AT_BLOCK; + handleTailRecursion(); + } + } + + /** + * "v is live-out at n." + */ + private void liveOutAtBlock() { + if (! visitedBlocks.get(blockN.getIndex())) { + visitedBlocks.set(blockN.getIndex()); + + blockN.addLiveOut(regV); + + ArrayList insns; + + insns = blockN.getInsns(); + + // Live out at last statement in blockN + statementIndex = insns.size() - 1; + nextFunction = NextFunction.LIVE_OUT_AT_STATEMENT; + } + } + + /** + * "v is live-in at s." + */ + private void liveInAtStatement() { + // if s is the first statement in block N + if (statementIndex == 0) { + // v is live-in at n + blockN.addLiveIn(regV); + + BitSet preds = blockN.getPredecessors(); + + liveOutBlocks.or(preds); + } else { + // Let s' be the statement preceeding s + statementIndex -= 1; + nextFunction = NextFunction.LIVE_OUT_AT_STATEMENT; + } + } + + /** + * "v is live-out at s." + */ + private void liveOutAtStatement() { + SsaInsn statement = blockN.getInsns().get(statementIndex); + RegisterSpec rs = statement.getResult(); + + if (!statement.isResultReg(regV)) { + if (rs != null) { + interference.add(regV, rs.getReg()); + } + nextFunction = NextFunction.LIVE_IN_AT_STATEMENT; + } + } + + /** + * Ensures that all the phi result registers for all the phis in the + * same basic block interfere with each other. This is needed since + * the dead code remover has allowed through "dead-end phis" whose + * results are not used except as local assignments. Without this step, + * a the result of a dead-end phi might be assigned the same register + * as the result of another phi, and the phi removal move scheduler may + * generate moves that over-write the live result. + * + * @param ssaMeth {@code non-null;} method to pricess + * @param interference {@code non-null;} interference graph + */ + private static void coInterferePhis(SsaMethod ssaMeth, + InterferenceGraph interference) { + for (SsaBasicBlock b : ssaMeth.getBlocks()) { + List phis = b.getPhiInsns(); + + int szPhis = phis.size(); + + for (int i = 0; i < szPhis; i++) { + for (int j = 0; j < szPhis; j++) { + if (i == j) { + continue; + } + + interference.add(phis.get(i).getResult().getReg(), + phis.get(j).getResult().getReg()); + } + } + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/back/NullRegisterAllocator.java b/dexlib/src/main/java/com/android/dx/ssa/back/NullRegisterAllocator.java new file mode 100644 index 000000000..adacdda98 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/back/NullRegisterAllocator.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa.back; + +import com.android.dx.ssa.BasicRegisterMapper; +import com.android.dx.ssa.RegisterMapper; +import com.android.dx.ssa.SsaMethod; + +/** + * A register allocator that maps SSA register n to Rop register 2*n, + * essentially preserving the original mapping and remaining agnostic + * about normal or wide categories. Used for debugging. + */ +public class NullRegisterAllocator extends RegisterAllocator { + /** {@inheritDoc} */ + public NullRegisterAllocator(SsaMethod ssaMeth, + InterferenceGraph interference) { + super(ssaMeth, interference); + } + + /** {@inheritDoc} */ + @Override + public boolean wantsParamsMovedHigh() { + // We're not smart enough for this. + return false; + } + + /** {@inheritDoc} */ + @Override + public RegisterMapper allocateRegisters() { + int oldRegCount = ssaMeth.getRegCount(); + + BasicRegisterMapper mapper = new BasicRegisterMapper(oldRegCount); + + for (int i = 0; i < oldRegCount; i++) { + mapper.addMapping(i, i*2, 2); + } + + return mapper; + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/back/RegisterAllocator.java b/dexlib/src/main/java/com/android/dx/ssa/back/RegisterAllocator.java new file mode 100644 index 000000000..9d5379809 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/back/RegisterAllocator.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa.back; + +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.ssa.NormalSsaInsn; +import com.android.dx.ssa.RegisterMapper; +import com.android.dx.ssa.SsaBasicBlock; +import com.android.dx.ssa.SsaInsn; +import com.android.dx.ssa.SsaMethod; +import com.android.dx.util.IntIterator; +import com.android.dx.util.IntSet; +import java.util.ArrayList; + +/** + * Base class of all register allocators. + */ +public abstract class RegisterAllocator { + /** method being processed */ + protected final SsaMethod ssaMeth; + + /** interference graph, indexed by register in both dimensions */ + protected final InterferenceGraph interference; + + /** + * Creates an instance. Call {@code allocateRegisters} to run. + * @param ssaMeth method to process. + * @param interference Interference graph, indexed by register in both + * dimensions. + */ + public RegisterAllocator(SsaMethod ssaMeth, + InterferenceGraph interference) { + this.ssaMeth = ssaMeth; + this.interference = interference; + } + + /** + * Indicates whether the method params were allocated at the bottom + * of the namespace, and thus should be moved up to the top of the + * namespace after phi removal. + * + * @return {@code true} if params should be moved from low to high + */ + public abstract boolean wantsParamsMovedHigh(); + + /** + * Runs the algorithm. + * + * @return a register mapper to apply to the {@code SsaMethod} + */ + public abstract RegisterMapper allocateRegisters(); + + /** + * Returns the category (width) of the definition site of the register. + * Returns {@code 1} for undefined registers. + * + * @param reg register + * @return {@code 1..2} + */ + protected final int getCategoryForSsaReg(int reg) { + SsaInsn definition = ssaMeth.getDefinitionForRegister(reg); + + if (definition == null) { + // an undefined reg + return 1; + } else { + return definition.getResult().getCategory(); + } + } + + /** + * Returns the RegisterSpec of the definition of the register. + * + * @param reg {@code >= 0;} SSA register + * @return definition spec of the register or null if it is never defined + * (for the case of "version 0" SSA registers) + */ + protected final RegisterSpec getDefinitionSpecForSsaReg(int reg) { + SsaInsn definition = ssaMeth.getDefinitionForRegister(reg); + + return definition == null ? null : definition.getResult(); + } + + /** + * Returns true if the definition site of this register is a + * move-param (ie, this is a method parameter). + * + * @param reg register in question + * @return {@code true} if this is a method parameter + */ + protected boolean isDefinitionMoveParam(int reg) { + SsaInsn defInsn = ssaMeth.getDefinitionForRegister(reg); + + if (defInsn instanceof NormalSsaInsn) { + NormalSsaInsn ndefInsn = (NormalSsaInsn) defInsn; + + return ndefInsn.getOpcode().getOpcode() == RegOps.MOVE_PARAM; + } + + return false; + } + + /** + * Inserts a move instruction for a specified SSA register before a + * specified instruction, creating a new SSA register and adjusting the + * interference graph in the process. The insn currently must be the + * last insn in a block. + * + * @param insn {@code non-null;} insn to insert move before, must + * be last insn in block + * @param reg {@code non-null;} SSA register to duplicate + * @return {@code non-null;} spec of new SSA register created by move + */ + protected final RegisterSpec insertMoveBefore(SsaInsn insn, + RegisterSpec reg) { + SsaBasicBlock block = insn.getBlock(); + ArrayList insns = block.getInsns(); + int insnIndex = insns.indexOf(insn); + + if (insnIndex < 0) { + throw new IllegalArgumentException ( + "specified insn is not in this block"); + } + + if (insnIndex != insns.size() - 1) { + /* + * Presently, the interference updater only works when + * adding before the last insn, and the last insn must have no + * result + */ + throw new IllegalArgumentException( + "Adding move here not supported:" + insn.toHuman()); + } + + /* + * Get new register and make new move instruction. + */ + + // The new result must not have an associated local variable. + RegisterSpec newRegSpec = RegisterSpec.make(ssaMeth.makeNewSsaReg(), + reg.getTypeBearer()); + + SsaInsn toAdd = SsaInsn.makeFromRop( + new PlainInsn(Rops.opMove(newRegSpec.getType()), + SourcePosition.NO_INFO, newRegSpec, + RegisterSpecList.make(reg)), block); + + insns.add(insnIndex, toAdd); + + int newReg = newRegSpec.getReg(); + + /* + * Adjust interference graph based on what's live out of the current + * block and what's used by the final instruction. + */ + + IntSet liveOut = block.getLiveOutRegs(); + IntIterator liveOutIter = liveOut.iterator(); + + while (liveOutIter.hasNext()) { + interference.add(newReg, liveOutIter.next()); + } + + // Everything that's a source in the last insn interferes. + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + + for (int i = 0; i < szSources; i++) { + interference.add(newReg, sources.get(i).getReg()); + } + + ssaMeth.onInsnsChanged(); + + return newRegSpec; + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/back/SsaToRop.java b/dexlib/src/main/java/com/android/dx/ssa/back/SsaToRop.java new file mode 100644 index 000000000..401c67f12 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/back/SsaToRop.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa.back; + +import com.android.dx.rop.code.BasicBlock; +import com.android.dx.rop.code.BasicBlockList; +import com.android.dx.rop.code.InsnList; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.Rops; +import com.android.dx.ssa.BasicRegisterMapper; +import com.android.dx.ssa.PhiInsn; +import com.android.dx.ssa.RegisterMapper; +import com.android.dx.ssa.SsaBasicBlock; +import com.android.dx.ssa.SsaInsn; +import com.android.dx.ssa.SsaMethod; +import com.android.dx.util.Hex; +import com.android.dx.util.IntList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Comparator; + +/** + * Converts a method in SSA form to ROP form. + */ +public class SsaToRop { + /** local debug flag */ + private static final boolean DEBUG = false; + + /** {@code non-null;} method to process */ + private final SsaMethod ssaMeth; + + /** + * {@code true} if the converter should attempt to minimize + * the rop-form register count + */ + private final boolean minimizeRegisters; + + /** {@code non-null;} interference graph */ + private final InterferenceGraph interference; + + /** + * Converts a method in SSA form to ROP form. + * + * @param ssaMeth {@code non-null;} method to process + * @param minimizeRegisters {@code true} if the converter should + * attempt to minimize the rop-form register count + * @return {@code non-null;} rop-form output + */ + public static RopMethod convertToRopMethod(SsaMethod ssaMeth, + boolean minimizeRegisters) { + return new SsaToRop(ssaMeth, minimizeRegisters).convert(); + } + + /** + * Constructs an instance. + * + * @param ssaMeth {@code non-null;} method to process + * @param minimizeRegisters {@code true} if the converter should + * attempt to minimize the rop-form register count + */ + private SsaToRop(SsaMethod ssaMethod, boolean minimizeRegisters) { + this.minimizeRegisters = minimizeRegisters; + this.ssaMeth = ssaMethod; + this.interference = + LivenessAnalyzer.constructInterferenceGraph(ssaMethod); + } + + /** + * Performs the conversion. + * + * @return {@code non-null;} rop-form output + */ + private RopMethod convert() { + if (DEBUG) { + interference.dumpToStdout(); + } + + // These are other allocators for debugging or historical comparison: + // allocator = new NullRegisterAllocator(ssaMeth, interference); + // allocator = new FirstFitAllocator(ssaMeth, interference); + + RegisterAllocator allocator = + new FirstFitLocalCombiningAllocator(ssaMeth, interference, + minimizeRegisters); + + RegisterMapper mapper = allocator.allocateRegisters(); + + if (DEBUG) { + System.out.println("Printing reg map"); + System.out.println(((BasicRegisterMapper)mapper).toHuman()); + } + + ssaMeth.setBackMode(); + + ssaMeth.mapRegisters(mapper); + + removePhiFunctions(); + + if (allocator.wantsParamsMovedHigh()) { + moveParametersToHighRegisters(); + } + + removeEmptyGotos(); + + RopMethod ropMethod = new RopMethod(convertBasicBlocks(), + ssaMeth.blockIndexToRopLabel(ssaMeth.getEntryBlockIndex())); + ropMethod = new IdenticalBlockCombiner(ropMethod).process(); + + return ropMethod; + } + + /** + * Removes all blocks containing only GOTOs from the control flow. + * Although much of this work will be done later when converting + * from rop to dex, not all simplification cases can be handled + * there. Furthermore, any no-op block between the exit block and + * blocks containing the real return or throw statements must be + * removed. + */ + private void removeEmptyGotos() { + final ArrayList blocks = ssaMeth.getBlocks(); + + ssaMeth.forEachBlockDepthFirst(false, new SsaBasicBlock.Visitor() { + public void visitBlock(SsaBasicBlock b, SsaBasicBlock parent) { + ArrayList insns = b.getInsns(); + + if ((insns.size() == 1) + && (insns.get(0).getOpcode() == Rops.GOTO)) { + BitSet preds = (BitSet) b.getPredecessors().clone(); + + for (int i = preds.nextSetBit(0); i >= 0; + i = preds.nextSetBit(i + 1)) { + SsaBasicBlock pb = blocks.get(i); + pb.replaceSuccessor(b.getIndex(), + b.getPrimarySuccessorIndex()); + } + } + } + }); + } + + /** + * See Appel 19.6. To remove the phi instructions in an edge-split + * SSA representation we know we can always insert a move in a + * predecessor block. + */ + private void removePhiFunctions() { + ArrayList blocks = ssaMeth.getBlocks(); + + for (SsaBasicBlock block : blocks) { + // Add moves in all the pred blocks for each phi insn. + block.forEachPhiInsn(new PhiVisitor(blocks)); + + // Delete the phi insns. + block.removeAllPhiInsns(); + } + + /* + * After all move insns have been added, sort them so they don't + * destructively interfere. + */ + for (SsaBasicBlock block : blocks) { + block.scheduleMovesFromPhis(); + } + } + + /** + * Helper for {@link #removePhiFunctions}: PhiSuccessorUpdater for + * adding move instructions to predecessors based on phi insns. + */ + private static class PhiVisitor implements PhiInsn.Visitor { + private final ArrayList blocks; + + public PhiVisitor(ArrayList blocks) { + this.blocks = blocks; + } + + public void visitPhiInsn(PhiInsn insn) { + RegisterSpecList sources = insn.getSources(); + RegisterSpec result = insn.getResult(); + int sz = sources.size(); + + for (int i = 0; i < sz; i++) { + RegisterSpec source = sources.get(i); + SsaBasicBlock predBlock = blocks.get( + insn.predBlockIndexForSourcesIndex(i)); + + predBlock.addMoveToEnd(result, source); + } + } + } + + /** + * Moves the parameter registers, which allocateRegisters() places + * at the bottom of the frame, up to the top of the frame to match + * Dalvik calling convention. + */ + private void moveParametersToHighRegisters() { + int paramWidth = ssaMeth.getParamWidth(); + BasicRegisterMapper mapper + = new BasicRegisterMapper(ssaMeth.getRegCount()); + int regCount = ssaMeth.getRegCount(); + + for (int i = 0; i < regCount; i++) { + if (i < paramWidth) { + mapper.addMapping(i, regCount - paramWidth + i, 1); + } else { + mapper.addMapping(i, i - paramWidth, 1); + } + } + + if (DEBUG) { + System.out.printf("Moving %d registers from 0 to %d\n", + paramWidth, regCount - paramWidth); + } + + ssaMeth.mapRegisters(mapper); + } + + /** + * @return rop-form basic block list + */ + private BasicBlockList convertBasicBlocks() { + ArrayList blocks = ssaMeth.getBlocks(); + + // Exit block may be null. + SsaBasicBlock exitBlock = ssaMeth.getExitBlock(); + + ssaMeth.computeReachability(); + int ropBlockCount = ssaMeth.getCountReachableBlocks(); + + // Don't count the exit block, if it exists and is reachable. + ropBlockCount -= (exitBlock != null && exitBlock.isReachable()) ? 1 : 0; + + BasicBlockList result = new BasicBlockList(ropBlockCount); + + // Convert all the reachable blocks except the exit block. + int ropBlockIndex = 0; + for (SsaBasicBlock b : blocks) { + if (b.isReachable() && b != exitBlock) { + result.set(ropBlockIndex++, convertBasicBlock(b)); + } + } + + // The exit block, which is discarded, must do nothing. + if (exitBlock != null && exitBlock.getInsns().size() != 0) { + throw new RuntimeException( + "Exit block must have no insns when leaving SSA form"); + } + + return result; + } + + /** + * Validates that a basic block is a valid end predecessor. It must + * end in a RETURN or a THROW. Throws a runtime exception on error. + * + * @param b {@code non-null;} block to validate + * @throws RuntimeException on error + */ + private void verifyValidExitPredecessor(SsaBasicBlock b) { + ArrayList insns = b.getInsns(); + SsaInsn lastInsn = insns.get(insns.size() - 1); + Rop opcode = lastInsn.getOpcode(); + + if (opcode.getBranchingness() != Rop.BRANCH_RETURN + && opcode != Rops.THROW) { + throw new RuntimeException("Exit predecessor must end" + + " in valid exit statement."); + } + } + + /** + * Converts a single basic block to rop form. + * + * @param block SSA block to process + * @return {@code non-null;} ROP block + */ + private BasicBlock convertBasicBlock(SsaBasicBlock block) { + IntList successorList = block.getRopLabelSuccessorList(); + int primarySuccessorLabel = block.getPrimarySuccessorRopLabel(); + + // Filter out any reference to the SSA form's exit block. + + // Exit block may be null. + SsaBasicBlock exitBlock = ssaMeth.getExitBlock(); + int exitRopLabel = (exitBlock == null) ? -1 : exitBlock.getRopLabel(); + + if (successorList.contains(exitRopLabel)) { + if (successorList.size() > 1) { + throw new RuntimeException( + "Exit predecessor must have no other successors" + + Hex.u2(block.getRopLabel())); + } else { + successorList = IntList.EMPTY; + primarySuccessorLabel = -1; + + verifyValidExitPredecessor(block); + } + } + + successorList.setImmutable(); + + BasicBlock result = new BasicBlock( + block.getRopLabel(), convertInsns(block.getInsns()), + successorList, + primarySuccessorLabel); + + return result; + } + + /** + * Converts an insn list to rop form. + * + * @param ssaInsns {@code non-null;} old instructions + * @return {@code non-null;} immutable instruction list + */ + private InsnList convertInsns(ArrayList ssaInsns) { + int insnCount = ssaInsns.size(); + InsnList result = new InsnList(insnCount); + + for (int i = 0; i < insnCount; i++) { + result.set(i, ssaInsns.get(i).toRopInsn()); + } + + result.setImmutable(); + + return result; + } + + /** + * Note: This method is not presently used. + * + * @return a list of registers ordered by most-frequently-used to + * least-frequently-used. Each register is listed once and only + * once. + */ + public int[] getRegistersByFrequency() { + int regCount = ssaMeth.getRegCount(); + Integer[] ret = new Integer[regCount]; + + for (int i = 0; i < regCount; i++) { + ret[i] = i; + } + + Arrays.sort(ret, new Comparator() { + public int compare(Integer o1, Integer o2) { + return ssaMeth.getUseListForRegister(o2).size() + - ssaMeth.getUseListForRegister(o1).size(); + } + }); + + int result[] = new int[regCount]; + + for (int i = 0; i < regCount; i++) { + result[i] = ret[i]; + } + + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dx/ssa/package-info.java b/dexlib/src/main/java/com/android/dx/ssa/package-info.java new file mode 100644 index 000000000..582a327eb --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/ssa/package-info.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.ssa; + +/** + *

      An introduction to SSA Form

      + * + * This package contains classes associated with dx's {@code SSA} + * intermediate form. This form is a static-single-assignment representation of + * Rop-form a method with Rop-form-like instructions (with the addition of a + * {@link PhiInsn phi instriction}. This form is intended to make it easy to + * implement basic optimization steps and register allocation so that a + * reasonably efficient register machine representation can be produced from a + * stack machine source bytecode.

      + * + *

      Key Classes

      + * + *

      Classes related to conversion and lifetime

      + *
        + *
      • {@link Optimizer} is a singleton class containing methods for + * converting, optimizing, and then back-converting Rop-form methods. It's the + * typical gateway into the rest of the package. + *
      • {@link SsaConverter} converts a Rop-form method to SSA form. + *
      • {@link SsaToRop} converts an SSA-form method back to Rop form. + *
      + * + *

      Classes related to method representation

      + *
        + *
      • A {@link SsaMethod} instance represents a method. + *
      • A {@link SsaBasicBlock} instance represents a basic block, whose + * semantics are quite similar to basic blocks in + * {@link com.android.dx.rop Rop form}. + *
      • {@link PhiInsn} instances represent "phi" operators defined in SSA + * literature. They must be the first N instructions in a basic block. + *
      • {@link NormalSsaInsn} instances represent instructions that directly + * correspond to {@code Rop} form. + *
      + * + *

      Classes related to optimization steps

      + *
        + *
      • {@link MoveParamCombiner} is a simple step that ensures each method + * parameter is represented by at most one SSA register. + *
      • {@link SCCP} is a (partially implemented) sparse-conditional + * constant propogator. + *
      • {@link LiteralOpUpgrader} is a step that attempts to use constant + * information to convert math and comparison instructions into + * constant-bearing "literal ops" in cases where they can be represented in the + * output form (see {@link TranslationAdvice#hasConstantOperation}). + *
      • {@link ConstCollector} is a step that attempts to trade (modest) + * increased register space for decreased instruction count in cases where + * the same constant value is used repeatedly in a single method. + *
      • {@link DeadCodeRemover} is a dead code remover. This phase must + * always be run to remove unused phi instructions. + *
      + * + *

      SSA Lifetime

      + * The representation of a method in SSA form obeys slightly different + * constraints depending upon whether it is in the process of being converted + * into or out of SSA form. + * + *

      Conversion into SSA Form

      + * + * {@link SsaConverter#convertToSsaMethod} takes a {@code RopMethod} and + * returns a fully-converted {@code SsaMethod}. The conversion process + * is roughly as follows: + * + *
        + *
      1. The Rop-form method, its blocks and their instructions are directly + * wrapped in {@code SsaMethod}, {@code SsaBasicBlock} and + * {@code SsaInsn} instances. Nothing else changes. + *
      2. Critical control-flow graph edges are {@link SsaConverter#edgeSplit + * split} and new basic blocks inserted as required to meet the constraints + * necessary for the ultimate SSA representation. + *
      3. A {@link LocalVariableExtractor} is run to produce a table of + * Rop registers to local variables necessary during phi placement. This + * step could also be done in Rop form and then updated through the preceding + * steps. + *
      4. {@code Phi} instructions are {link SsaConverter#placePhiFunctions} + * placed in a semi-pruned fashion, which requires computation of {@link + * Dominators dominance graph} and each node's {@link DomFront + * dominance-frontier set}. + *
      5. Finally, source and result registers for all instructions are {@link + * SsaRenamer renamed} such that each assignment is given a unique register + * number (register categories or widths, significant in Rop form, do not + * exist in SSA). Move instructions are eliminated except where necessary + * to preserve local variable assignments. + *
      + * + */ diff --git a/dexlib/src/main/java/com/android/dx/util/AnnotatedOutput.java b/dexlib/src/main/java/com/android/dx/util/AnnotatedOutput.java new file mode 100644 index 000000000..7a9ea29d4 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/AnnotatedOutput.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * Interface for a binary output destination that may be augmented + * with textual annotations. + */ +public interface AnnotatedOutput + extends Output { + /** + * Get whether this instance will actually keep annotations. + * + * @return {@code true} iff annotations are being kept + */ + public boolean annotates(); + + /** + * Get whether this instance is intended to keep verbose annotations. + * Annotators may use the result of calling this method to inform their + * annotation activity. + * + * @return {@code true} iff annotations are to be verbose + */ + public boolean isVerbose(); + + /** + * Add an annotation for the subsequent output. Any previously + * open annotation will be closed by this call, and the new + * annotation marks all subsequent output until another annotation + * call. + * + * @param msg {@code non-null;} the annotation message + */ + public void annotate(String msg); + + /** + * Add an annotation for a specified amount of subsequent + * output. Any previously open annotation will be closed by this + * call. If there is already pending annotation from one or more + * previous calls to this method, the new call "consumes" output + * after all the output covered by the previous calls. + * + * @param amt {@code >= 0;} the amount of output for this annotation to + * cover + * @param msg {@code non-null;} the annotation message + */ + public void annotate(int amt, String msg); + + /** + * End the most recent annotation. Subsequent output will be unannotated, + * until the next call to {@link #annotate}. + */ + public void endAnnotation(); + + /** + * Get the maximum width of the annotated output. This is advisory: + * Implementations of this interface are encouraged to deal with too-wide + * output, but annotaters are encouraged to attempt to avoid exceeding + * the indicated width. + * + * @return {@code >= 1;} the maximum width + */ + public int getAnnotationWidth(); +} diff --git a/dexlib/src/main/java/com/android/dx/util/BitIntSet.java b/dexlib/src/main/java/com/android/dx/util/BitIntSet.java new file mode 100644 index 000000000..8a75ed683 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/BitIntSet.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import java.util.NoSuchElementException; + +/** + * A set of integers, represented by a bit set + */ +public class BitIntSet implements IntSet { + + /** also accessed in ListIntSet */ + int[] bits; + + /** + * Constructs an instance. + * + * @param max the maximum value of ints in this set. + */ + public BitIntSet(int max) { + bits = Bits.makeBitSet(max); + } + + /** {@inheritDoc} */ + public void add(int value) { + ensureCapacity(value); + Bits.set(bits, value, true); + } + + /** + * Ensures that the bit set has the capacity to represent the given value. + * + * @param value {@code >= 0;} value to represent + */ + private void ensureCapacity(int value) { + if (value >= Bits.getMax(bits)) { + int[] newBits = Bits.makeBitSet( + Math.max(value + 1, 2 * Bits.getMax(bits))); + System.arraycopy(bits, 0, newBits, 0, bits.length); + bits = newBits; + } + } + + /** {@inheritDoc} */ + public void remove(int value) { + if (value < Bits.getMax(bits)) { + Bits.set(bits, value, false); + } + } + + /** {@inheritDoc} */ + public boolean has(int value) { + return (value < Bits.getMax(bits)) && Bits.get(bits, value); + } + + /** {@inheritDoc} */ + public void merge(IntSet other) { + if (other instanceof BitIntSet) { + BitIntSet o = (BitIntSet) other; + ensureCapacity(Bits.getMax(o.bits) + 1); + Bits.or(bits, o.bits); + } else if (other instanceof ListIntSet) { + ListIntSet o = (ListIntSet) other; + int sz = o.ints.size(); + + if (sz > 0) { + ensureCapacity(o.ints.get(sz - 1)); + } + for (int i = 0; i < o.ints.size(); i++) { + Bits.set(bits, o.ints.get(i), true); + } + } else { + IntIterator iter = other.iterator(); + while (iter.hasNext()) { + add(iter.next()); + } + } + } + + /** {@inheritDoc} */ + public int elements() { + return Bits.bitCount(bits); + } + + /** {@inheritDoc} */ + public IntIterator iterator() { + return new IntIterator() { + private int idx = Bits.findFirst(bits, 0); + + /** {@inheritDoc} */ + public boolean hasNext() { + return idx >= 0; + } + + /** {@inheritDoc} */ + public int next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + int ret = idx; + + idx = Bits.findFirst(bits, idx+1); + + return ret; + } + }; + } + + /** {@inheritDoc} */ + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append('{'); + + boolean first = true; + for (int i = Bits.findFirst(bits, 0) + ; i >= 0 + ; i = Bits.findFirst(bits, i + 1)) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append(i); + } + + sb.append('}'); + + return sb.toString(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/Bits.java b/dexlib/src/main/java/com/android/dx/util/Bits.java new file mode 100644 index 000000000..cbc0a5b6b --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/Bits.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * Utilities for treating {@code int[]}s as bit sets. + */ +public final class Bits { + /** + * This class is uninstantiable. + */ + private Bits() { + // This space intentionally left blank. + } + + /** + * Constructs a bit set to contain bits up to the given index (exclusive). + * + * @param max {@code >= 0;} the maximum bit index (exclusive) + * @return {@code non-null;} an appropriately-constructed instance + */ + public static int[] makeBitSet(int max) { + int size = (max + 0x1f) >> 5; + return new int[size]; + } + + /** + * Gets the maximum index (exclusive) for the given bit set. + * + * @param bits {@code non-null;} bit set in question + * @return {@code >= 0;} the maximum index (exclusive) that may be set + */ + public static int getMax(int[] bits) { + return bits.length * 0x20; + } + + /** + * Gets the value of the bit at the given index. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + * @return the value of the indicated bit + */ + public static boolean get(int[] bits, int idx) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + return (bits[arrayIdx] & bit) != 0; + } + + /** + * Sets the given bit to the given value. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + * @param value the new value for the bit + */ + public static void set(int[] bits, int idx, boolean value) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + + if (value) { + bits[arrayIdx] |= bit; + } else { + bits[arrayIdx] &= ~bit; + } + } + + /** + * Sets the given bit to {@code true}. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + */ + public static void set(int[] bits, int idx) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + bits[arrayIdx] |= bit; + } + + /** + * Sets the given bit to {@code false}. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + */ + public static void clear(int[] bits, int idx) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + bits[arrayIdx] &= ~bit; + } + + /** + * Returns whether or not the given bit set is empty, that is, whether + * no bit is set to {@code true}. + * + * @param bits {@code non-null;} bit set to operate on + * @return {@code true} iff all bits are {@code false} + */ + public static boolean isEmpty(int[] bits) { + int len = bits.length; + + for (int i = 0; i < len; i++) { + if (bits[i] != 0) { + return false; + } + } + + return true; + } + + /** + * Gets the number of bits set to {@code true} in the given bit set. + * + * @param bits {@code non-null;} bit set to operate on + * @return {@code >= 0;} the bit count (aka population count) of the set + */ + public static int bitCount(int[] bits) { + int len = bits.length; + int count = 0; + + for (int i = 0; i < len; i++) { + count += Integer.bitCount(bits[i]); + } + + return count; + } + + /** + * Returns whether any bits are set to {@code true} in the + * specified range. + * + * @param bits {@code non-null;} bit set to operate on + * @param start {@code >= 0;} index of the first bit in the range (inclusive) + * @param end {@code >= 0;} index of the last bit in the range (exclusive) + * @return {@code true} if any bit is set to {@code true} in + * the indicated range + */ + public static boolean anyInRange(int[] bits, int start, int end) { + int idx = findFirst(bits, start); + return (idx >= 0) && (idx < end); + } + + /** + * Finds the lowest-order bit set at or after the given index in the + * given bit set. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0;} minimum index to return + * @return {@code >= -1;} lowest-order bit set at or after {@code idx}, + * or {@code -1} if there is no appropriate bit index to return + */ + public static int findFirst(int[] bits, int idx) { + int len = bits.length; + int minBit = idx & 0x1f; + + for (int arrayIdx = idx >> 5; arrayIdx < len; arrayIdx++) { + int word = bits[arrayIdx]; + if (word != 0) { + int bitIdx = findFirst(word, minBit); + if (bitIdx >= 0) { + return (arrayIdx << 5) + bitIdx; + } + } + minBit = 0; + } + + return -1; + } + + /** + * Finds the lowest-order bit set at or after the given index in the + * given {@code int}. + * + * @param value the value in question + * @param idx 0..31 the minimum bit index to return + * @return {@code >= -1;} lowest-order bit set at or after {@code idx}, + * or {@code -1} if there is no appropriate bit index to return + */ + public static int findFirst(int value, int idx) { + value &= ~((1 << idx) - 1); // Mask off too-low bits. + int result = Integer.numberOfTrailingZeros(value); + return (result == 32) ? -1 : result; + } + + /** + * Ors bit array {@code b} into bit array {@code a}. + * {@code a.length} must be greater than or equal to + * {@code b.length}. + * + * @param a {@code non-null;} int array to be ored with other argument. This + * argument is modified. + * @param b {@code non-null;} int array to be ored into {@code a}. This + * argument is not modified. + */ + public static void or(int[] a, int[] b) { + for (int i = 0; i < b.length; i++) { + a[i] |= b[i]; + } + } + + public static String toHuman(int[] bits) { + StringBuilder sb = new StringBuilder(); + + boolean needsComma = false; + + sb.append('{'); + + int bitsLength = 32 * bits.length; + for (int i = 0; i < bitsLength; i++) { + if (Bits.get(bits, i)) { + if (needsComma) { + sb.append(','); + } + needsComma = true; + sb.append(i); + } + } + sb.append('}'); + + return sb.toString(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/ByteArray.java b/dexlib/src/main/java/com/android/dx/util/ByteArray.java new file mode 100644 index 000000000..bc885ad75 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/ByteArray.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Wrapper for a {@code byte[]}, which provides read-only access and + * can "reveal" a partial slice of the underlying array. + * + * Note: Multibyte accessors all use big-endian order. + */ +public final class ByteArray { + /** {@code non-null;} underlying array */ + private final byte[] bytes; + + /** {@code >= 0}; start index of the slice (inclusive) */ + private final int start; + + /** {@code >= 0, <= bytes.length}; size computed as + * {@code end - start} (in the constructor) */ + private final int size; + + /** + * Constructs an instance. + * + * @param bytes {@code non-null;} the underlying array + * @param start {@code >= 0;} start index of the slice (inclusive) + * @param end {@code >= start, <= bytes.length;} end index of + * the slice (exclusive) + */ + public ByteArray(byte[] bytes, int start, int end) { + if (bytes == null) { + throw new NullPointerException("bytes == null"); + } + + if (start < 0) { + throw new IllegalArgumentException("start < 0"); + } + + if (end < start) { + throw new IllegalArgumentException("end < start"); + } + + if (end > bytes.length) { + throw new IllegalArgumentException("end > bytes.length"); + } + + this.bytes = bytes; + this.start = start; + this.size = end - start; + } + + /** + * Constructs an instance from an entire {@code byte[]}. + * + * @param bytes {@code non-null;} the underlying array + */ + public ByteArray(byte[] bytes) { + this(bytes, 0, bytes.length); + } + + /** + * Gets the size of the array, in bytes. + * + * @return {@code >= 0;} the size + */ + public int size() { + return size; + } + + /** + * Returns a slice (that is, a sub-array) of this instance. + * + * @param start {@code >= 0;} start index of the slice (inclusive) + * @param end {@code >= start, <= size();} end index of + * the slice (exclusive) + * @return {@code non-null;} the slice + */ + public ByteArray slice(int start, int end) { + checkOffsets(start, end); + return new ByteArray(bytes, start + this.start, end + this.start); + } + + /** + * Returns the offset into the given array represented by the given + * offset into this instance. + * + * @param offset offset into this instance + * @param bytes {@code non-null;} (alleged) underlying array + * @return corresponding offset into {@code bytes} + * @throws IllegalArgumentException thrown if {@code bytes} is + * not the underlying array of this instance + */ + public int underlyingOffset(int offset, byte[] bytes) { + if (bytes != this.bytes) { + throw new IllegalArgumentException("wrong bytes"); + } + + return start + offset; + } + + /** + * Gets the {@code signed byte} value at a particular offset. + * + * @param off {@code >= 0, < size();} offset to fetch + * @return {@code signed byte} at that offset + */ + public int getByte(int off) { + checkOffsets(off, off + 1); + return getByte0(off); + } + + /** + * Gets the {@code signed short} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 1);} offset to fetch + * @return {@code signed short} at that offset + */ + public int getShort(int off) { + checkOffsets(off, off + 2); + return (getByte0(off) << 8) | getUnsignedByte0(off + 1); + } + + /** + * Gets the {@code signed int} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 3);} offset to fetch + * @return {@code signed int} at that offset + */ + public int getInt(int off) { + checkOffsets(off, off + 4); + return (getByte0(off) << 24) | + (getUnsignedByte0(off + 1) << 16) | + (getUnsignedByte0(off + 2) << 8) | + getUnsignedByte0(off + 3); + } + + /** + * Gets the {@code signed long} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 7);} offset to fetch + * @return {@code signed int} at that offset + */ + public long getLong(int off) { + checkOffsets(off, off + 8); + int part1 = (getByte0(off) << 24) | + (getUnsignedByte0(off + 1) << 16) | + (getUnsignedByte0(off + 2) << 8) | + getUnsignedByte0(off + 3); + int part2 = (getByte0(off + 4) << 24) | + (getUnsignedByte0(off + 5) << 16) | + (getUnsignedByte0(off + 6) << 8) | + getUnsignedByte0(off + 7); + + return (part2 & 0xffffffffL) | ((long) part1) << 32; + } + + /** + * Gets the {@code unsigned byte} value at a particular offset. + * + * @param off {@code >= 0, < size();} offset to fetch + * @return {@code unsigned byte} at that offset + */ + public int getUnsignedByte(int off) { + checkOffsets(off, off + 1); + return getUnsignedByte0(off); + } + + /** + * Gets the {@code unsigned short} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 1);} offset to fetch + * @return {@code unsigned short} at that offset + */ + public int getUnsignedShort(int off) { + checkOffsets(off, off + 2); + return (getUnsignedByte0(off) << 8) | getUnsignedByte0(off + 1); + } + + /** + * Copies the contents of this instance into the given raw + * {@code byte[]} at the given offset. The given array must be + * large enough. + * + * @param out {@code non-null;} array to hold the output + * @param offset {@code non-null;} index into {@code out} for the first + * byte of output + */ + public void getBytes(byte[] out, int offset) { + if ((out.length - offset) < size) { + throw new IndexOutOfBoundsException("(out.length - offset) < " + + "size()"); + } + + System.arraycopy(bytes, start, out, offset, size); + } + + /** + * Checks a range of offsets for validity, throwing if invalid. + * + * @param s start offset (inclusive) + * @param e end offset (exclusive) + */ + private void checkOffsets(int s, int e) { + if ((s < 0) || (e < s) || (e > size)) { + throw new IllegalArgumentException("bad range: " + s + ".." + e + + "; actual size " + size); + } + } + + /** + * Gets the {@code signed byte} value at the given offset, + * without doing any argument checking. + * + * @param off offset to fetch + * @return byte at that offset + */ + private int getByte0(int off) { + return bytes[start + off]; + } + + /** + * Gets the {@code unsigned byte} value at the given offset, + * without doing any argument checking. + * + * @param off offset to fetch + * @return byte at that offset + */ + private int getUnsignedByte0(int off) { + return bytes[start + off] & 0xff; + } + + /** + * Gets a {@code DataInputStream} that reads from this instance, + * with the cursor starting at the beginning of this instance's data. + * Note: The returned instance may be cast to {@link GetCursor} + * if needed. + * + * @return {@code non-null;} an appropriately-constructed + * {@code DataInputStream} instance + */ + public MyDataInputStream makeDataInputStream() { + return new MyDataInputStream(makeInputStream()); + } + + /** + * Gets a {@code InputStream} that reads from this instance, + * with the cursor starting at the beginning of this instance's data. + * Note: The returned instance may be cast to {@link GetCursor} + * if needed. + * + * @return {@code non-null;} an appropriately-constructed + * {@code InputStream} instancex + */ + public MyInputStream makeInputStream() { + return new MyInputStream(); + } + + /** + * Helper interface that allows one to get the cursor (of a stream). + */ + public interface GetCursor { + /** + * Gets the current cursor. + * + * @return {@code 0..size();} the cursor + */ + public int getCursor(); + } + + /** + * Helper class for {@link #makeInputStream}, which implements the + * stream functionality. + */ + public class MyInputStream extends InputStream { + /** 0..size; the cursor */ + private int cursor; + + /** 0..size; the mark */ + private int mark; + + public MyInputStream() { + cursor = 0; + mark = 0; + } + + public int read() throws IOException { + if (cursor >= size) { + return -1; + } + + int result = getUnsignedByte0(cursor); + cursor++; + return result; + } + + public int read(byte[] arr, int offset, int length) { + if ((offset + length) > arr.length) { + length = arr.length - offset; + } + + int maxLength = size - cursor; + if (length > maxLength) { + length = maxLength; + } + + System.arraycopy(bytes, cursor + start, arr, offset, length); + cursor += length; + return length; + } + + public int available() { + return size - cursor; + } + + public void mark(int reserve) { + mark = cursor; + } + + public void reset() { + cursor = mark; + } + + public boolean markSupported() { + return true; + } + } + + /** + * Helper class for {@link #makeDataInputStream}. This is used + * simply so that the cursor of a wrapped {@link MyInputStream} + * instance may be easily determined. + */ + public static class MyDataInputStream extends DataInputStream { + /** {@code non-null;} the underlying {@link MyInputStream} */ + private final MyInputStream wrapped; + + public MyDataInputStream(MyInputStream wrapped) { + super(wrapped); + + this.wrapped = wrapped; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/ByteArrayAnnotatedOutput.java b/dexlib/src/main/java/com/android/dx/util/ByteArrayAnnotatedOutput.java new file mode 100644 index 000000000..187d88656 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/ByteArrayAnnotatedOutput.java @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import com.android.dex.util.ByteOutput; +import com.android.dex.util.ExceptionWithContext; +import com.android.dex.Leb128; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; + +/** + * Implementation of {@link AnnotatedOutput} which stores the written data + * into a {@code byte[]}. + * + *

      Note: As per the {@link Output} interface, multi-byte + * writes all use little-endian order.

      + */ +public final class ByteArrayAnnotatedOutput + implements AnnotatedOutput, ByteOutput { + /** default size for stretchy instances */ + private static final int DEFAULT_SIZE = 1000; + + /** + * whether the instance is stretchy, that is, whether its array + * may be resized to increase capacity + */ + private final boolean stretchy; + + /** {@code non-null;} the data itself */ + private byte[] data; + + /** {@code >= 0;} current output cursor */ + private int cursor; + + /** whether annotations are to be verbose */ + private boolean verbose; + + /** + * {@code null-ok;} list of annotations, or {@code null} if this instance + * isn't keeping them + */ + private ArrayList annotations; + + /** {@code >= 40 (if used);} the desired maximum annotation width */ + private int annotationWidth; + + /** + * {@code >= 8 (if used);} the number of bytes of hex output to use + * in annotations + */ + private int hexCols; + + /** + * Constructs an instance with a fixed maximum size. Note that the + * given array is the only one that will be used to store data. In + * particular, no reallocation will occur in order to expand the + * capacity of the resulting instance. Also, the constructed + * instance does not keep annotations by default. + * + * @param data {@code non-null;} data array to use for output + */ + public ByteArrayAnnotatedOutput(byte[] data) { + this(data, false); + } + + /** + * Constructs a "stretchy" instance. The underlying array may be + * reallocated. The constructed instance does not keep annotations + * by default. + */ + public ByteArrayAnnotatedOutput() { + this(DEFAULT_SIZE); + } + + /** + * Constructs a "stretchy" instance with initial size {@code size}. The + * underlying array may be reallocated. The constructed instance does not + * keep annotations by default. + */ + public ByteArrayAnnotatedOutput(int size) { + this(new byte[size], true); + } + + /** + * Internal constructor. + * + * @param data {@code non-null;} data array to use for output + * @param stretchy whether the instance is to be stretchy + */ + private ByteArrayAnnotatedOutput(byte[] data, boolean stretchy) { + if (data == null) { + throw new NullPointerException("data == null"); + } + + this.stretchy = stretchy; + this.data = data; + this.cursor = 0; + this.verbose = false; + this.annotations = null; + this.annotationWidth = 0; + this.hexCols = 0; + } + + /** + * Gets the underlying {@code byte[]} of this instance, which + * may be larger than the number of bytes written + * + * @see #toByteArray + * + * @return {@code non-null;} the {@code byte[]} + */ + public byte[] getArray() { + return data; + } + + /** + * Constructs and returns a new {@code byte[]} that contains + * the written contents exactly (that is, with no extra unwritten + * bytes at the end). + * + * @see #getArray + * + * @return {@code non-null;} an appropriately-constructed array + */ + public byte[] toByteArray() { + byte[] result = new byte[cursor]; + System.arraycopy(data, 0, result, 0, cursor); + return result; + } + + /** {@inheritDoc} */ + public int getCursor() { + return cursor; + } + + /** {@inheritDoc} */ + public void assertCursor(int expectedCursor) { + if (cursor != expectedCursor) { + throw new ExceptionWithContext("expected cursor " + + expectedCursor + "; actual value: " + cursor); + } + } + + /** {@inheritDoc} */ + public void writeByte(int value) { + int writeAt = cursor; + int end = writeAt + 1; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + data[writeAt] = (byte) value; + cursor = end; + } + + /** {@inheritDoc} */ + public void writeShort(int value) { + int writeAt = cursor; + int end = writeAt + 2; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + data[writeAt] = (byte) value; + data[writeAt + 1] = (byte) (value >> 8); + cursor = end; + } + + /** {@inheritDoc} */ + public void writeInt(int value) { + int writeAt = cursor; + int end = writeAt + 4; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + data[writeAt] = (byte) value; + data[writeAt + 1] = (byte) (value >> 8); + data[writeAt + 2] = (byte) (value >> 16); + data[writeAt + 3] = (byte) (value >> 24); + cursor = end; + } + + /** {@inheritDoc} */ + public void writeLong(long value) { + int writeAt = cursor; + int end = writeAt + 8; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + int half = (int) value; + data[writeAt] = (byte) half; + data[writeAt + 1] = (byte) (half >> 8); + data[writeAt + 2] = (byte) (half >> 16); + data[writeAt + 3] = (byte) (half >> 24); + + half = (int) (value >> 32); + data[writeAt + 4] = (byte) half; + data[writeAt + 5] = (byte) (half >> 8); + data[writeAt + 6] = (byte) (half >> 16); + data[writeAt + 7] = (byte) (half >> 24); + + cursor = end; + } + + /** {@inheritDoc} */ + public int writeUleb128(int value) { + if (stretchy) { + ensureCapacity(cursor + 5); // pessimistic + } + int cursorBefore = cursor; + Leb128.writeUnsignedLeb128(this, value); + return (cursor - cursorBefore); + } + + /** {@inheritDoc} */ + public int writeSleb128(int value) { + if (stretchy) { + ensureCapacity(cursor + 5); // pessimistic + } + int cursorBefore = cursor; + Leb128.writeSignedLeb128(this, value); + return (cursor - cursorBefore); + } + + /** {@inheritDoc} */ + public void write(ByteArray bytes) { + int blen = bytes.size(); + int writeAt = cursor; + int end = writeAt + blen; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + bytes.getBytes(data, writeAt); + cursor = end; + } + + /** {@inheritDoc} */ + public void write(byte[] bytes, int offset, int length) { + int writeAt = cursor; + int end = writeAt + length; + int bytesEnd = offset + length; + + // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0) + if (((offset | length | end) < 0) || (bytesEnd > bytes.length)) { + throw new IndexOutOfBoundsException("bytes.length " + + bytes.length + "; " + + offset + "..!" + end); + } + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + System.arraycopy(bytes, offset, data, writeAt, length); + cursor = end; + } + + /** {@inheritDoc} */ + public void write(byte[] bytes) { + write(bytes, 0, bytes.length); + } + + /** {@inheritDoc} */ + public void writeZeroes(int count) { + if (count < 0) { + throw new IllegalArgumentException("count < 0"); + } + + int end = cursor + count; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + /* + * There is no need to actually write zeroes, since the array is + * already preinitialized with zeroes. + */ + + cursor = end; + } + + /** {@inheritDoc} */ + public void alignTo(int alignment) { + int mask = alignment - 1; + + if ((alignment < 0) || ((mask & alignment) != 0)) { + throw new IllegalArgumentException("bogus alignment"); + } + + int end = (cursor + mask) & ~mask; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + /* + * There is no need to actually write zeroes, since the array is + * already preinitialized with zeroes. + */ + + cursor = end; + } + + /** {@inheritDoc} */ + public boolean annotates() { + return (annotations != null); + } + + /** {@inheritDoc} */ + public boolean isVerbose() { + return verbose; + } + + /** {@inheritDoc} */ + public void annotate(String msg) { + if (annotations == null) { + return; + } + + endAnnotation(); + annotations.add(new Annotation(cursor, msg)); + } + + /** {@inheritDoc} */ + public void annotate(int amt, String msg) { + if (annotations == null) { + return; + } + + endAnnotation(); + + int asz = annotations.size(); + int lastEnd = (asz == 0) ? 0 : annotations.get(asz - 1).getEnd(); + int startAt; + + if (lastEnd <= cursor) { + startAt = cursor; + } else { + startAt = lastEnd; + } + + annotations.add(new Annotation(startAt, startAt + amt, msg)); + } + + /** {@inheritDoc} */ + public void endAnnotation() { + if (annotations == null) { + return; + } + + int sz = annotations.size(); + + if (sz != 0) { + annotations.get(sz - 1).setEndIfUnset(cursor); + } + } + + /** {@inheritDoc} */ + public int getAnnotationWidth() { + int leftWidth = 8 + (hexCols * 2) + (hexCols / 2); + + return annotationWidth - leftWidth; + } + + /** + * Indicates that this instance should keep annotations. This method may + * be called only once per instance, and only before any data has been + * written to the it. + * + * @param annotationWidth {@code >= 40;} the desired maximum annotation width + * @param verbose whether or not to indicate verbose annotations + */ + public void enableAnnotations(int annotationWidth, boolean verbose) { + if ((annotations != null) || (cursor != 0)) { + throw new RuntimeException("cannot enable annotations"); + } + + if (annotationWidth < 40) { + throw new IllegalArgumentException("annotationWidth < 40"); + } + + int hexCols = (((annotationWidth - 7) / 15) + 1) & ~1; + if (hexCols < 6) { + hexCols = 6; + } else if (hexCols > 10) { + hexCols = 10; + } + + this.annotations = new ArrayList(1000); + this.annotationWidth = annotationWidth; + this.hexCols = hexCols; + this.verbose = verbose; + } + + /** + * Finishes up annotation processing. This closes off any open + * annotations and removes annotations that don't refer to written + * data. + */ + public void finishAnnotating() { + // Close off the final annotation, if any. + endAnnotation(); + + // Remove annotations that refer to unwritten data. + if (annotations != null) { + int asz = annotations.size(); + while (asz > 0) { + Annotation last = annotations.get(asz - 1); + if (last.getStart() > cursor) { + annotations.remove(asz - 1); + asz--; + } else if (last.getEnd() > cursor) { + last.setEnd(cursor); + break; + } else { + break; + } + } + } + } + + /** + * Writes the annotated content of this instance to the given writer. + * + * @param out {@code non-null;} where to write to + */ + public void writeAnnotationsTo(Writer out) throws IOException { + int width2 = getAnnotationWidth(); + int width1 = annotationWidth - width2 - 1; + + TwoColumnOutput twoc = new TwoColumnOutput(out, width1, width2, "|"); + Writer left = twoc.getLeft(); + Writer right = twoc.getRight(); + int leftAt = 0; // left-hand byte output cursor + int rightAt = 0; // right-hand annotation index + int rightSz = annotations.size(); + + while ((leftAt < cursor) && (rightAt < rightSz)) { + Annotation a = annotations.get(rightAt); + int start = a.getStart(); + int end; + String text; + + if (leftAt < start) { + // This is an area with no annotation. + end = start; + start = leftAt; + text = ""; + } else { + // This is an area with an annotation. + end = a.getEnd(); + text = a.getText(); + rightAt++; + } + + left.write(Hex.dump(data, start, end - start, start, hexCols, 6)); + right.write(text); + twoc.flush(); + leftAt = end; + } + + if (leftAt < cursor) { + // There is unannotated output at the end. + left.write(Hex.dump(data, leftAt, cursor - leftAt, leftAt, + hexCols, 6)); + } + + while (rightAt < rightSz) { + // There are zero-byte annotations at the end. + right.write(annotations.get(rightAt).getText()); + rightAt++; + } + + twoc.flush(); + } + + /** + * Throws the excpetion for when an attempt is made to write past the + * end of the instance. + */ + private static void throwBounds() { + throw new IndexOutOfBoundsException("attempt to write past the end"); + } + + /** + * Reallocates the underlying array if necessary. Calls to this method + * should be guarded by a test of {@link #stretchy}. + * + * @param desiredSize {@code >= 0;} the desired minimum total size of the array + */ + private void ensureCapacity(int desiredSize) { + if (data.length < desiredSize) { + byte[] newData = new byte[desiredSize * 2 + 1000]; + System.arraycopy(data, 0, newData, 0, cursor); + data = newData; + } + } + + /** + * Annotation on output. + */ + private static class Annotation { + /** {@code >= 0;} start of annotated range (inclusive) */ + private final int start; + + /** + * {@code >= 0;} end of annotated range (exclusive); + * {@code Integer.MAX_VALUE} if unclosed + */ + private int end; + + /** {@code non-null;} annotation text */ + private final String text; + + /** + * Constructs an instance. + * + * @param start {@code >= 0;} start of annotated range + * @param end {@code >= start;} end of annotated range (exclusive) or + * {@code Integer.MAX_VALUE} if unclosed + * @param text {@code non-null;} annotation text + */ + public Annotation(int start, int end, String text) { + this.start = start; + this.end = end; + this.text = text; + } + + /** + * Constructs an instance. It is initally unclosed. + * + * @param start {@code >= 0;} start of annotated range + * @param text {@code non-null;} annotation text + */ + public Annotation(int start, String text) { + this(start, Integer.MAX_VALUE, text); + } + + /** + * Sets the end as given, but only if the instance is unclosed; + * otherwise, do nothing. + * + * @param end {@code >= start;} the end + */ + public void setEndIfUnset(int end) { + if (this.end == Integer.MAX_VALUE) { + this.end = end; + } + } + + /** + * Sets the end as given. + * + * @param end {@code >= start;} the end + */ + public void setEnd(int end) { + this.end = end; + } + + /** + * Gets the start. + * + * @return the start + */ + public int getStart() { + return start; + } + + /** + * Gets the end. + * + * @return the end + */ + public int getEnd() { + return end; + } + + /** + * Gets the text. + * + * @return {@code non-null;} the text + */ + public String getText() { + return text; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/FixedSizeList.java b/dexlib/src/main/java/com/android/dx/util/FixedSizeList.java new file mode 100644 index 000000000..fb3af0450 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/FixedSizeList.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import java.util.Arrays; + +/** + * Simple (mostly) fixed-size list of objects, which may be made immutable. + */ +public class FixedSizeList + extends MutabilityControl implements ToHuman { + /** {@code non-null;} array of elements */ + private Object[] arr; + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public FixedSizeList(int size) { + super(size != 0); + + try { + arr = new Object[size]; + } catch (NegativeArraySizeException ex) { + // Translate the exception. + throw new IllegalArgumentException("size < 0"); + } + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + // Easy out. + return true; + } + + if ((other == null) || (getClass() != other.getClass())) { + // Another easy out. + return false; + } + + FixedSizeList list = (FixedSizeList) other; + return Arrays.equals(arr, list.arr); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Arrays.hashCode(arr); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + String name = getClass().getName(); + + return toString0(name.substring(name.lastIndexOf('.') + 1) + '{', + ", ", + "}", + false); + } + + /** + * {@inheritDoc} + * + * This method will only work if every element of the list + * implements {@link ToHuman}. + */ + public String toHuman() { + String name = getClass().getName(); + + return toString0(name.substring(name.lastIndexOf('.') + 1) + '{', + ", ", + "}", + true); + } + + /** + * Gets a customized string form for this instance. + * + * @param prefix {@code null-ok;} prefix for the start of the result + * @param separator {@code null-ok;} separator to insert between each item + * @param suffix {@code null-ok;} suffix for the end of the result + * @return {@code non-null;} the custom string + */ + public String toString(String prefix, String separator, String suffix) { + return toString0(prefix, separator, suffix, false); + } + + /** + * Gets a customized human string for this instance. This method will + * only work if every element of the list implements {@link + * ToHuman}. + * + * @param prefix {@code null-ok;} prefix for the start of the result + * @param separator {@code null-ok;} separator to insert between each item + * @param suffix {@code null-ok;} suffix for the end of the result + * @return {@code non-null;} the custom string + */ + public String toHuman(String prefix, String separator, String suffix) { + return toString0(prefix, separator, suffix, true); + } + + /** + * Gets the number of elements in this list. + */ + public final int size() { + return arr.length; + } + + /** + * Shrinks this instance to fit, by removing any unset + * ({@code null}) elements, leaving the remaining elements in + * their original order. + */ + public void shrinkToFit() { + int sz = arr.length; + int newSz = 0; + + for (int i = 0; i < sz; i++) { + if (arr[i] != null) { + newSz++; + } + } + + if (sz == newSz) { + return; + } + + throwIfImmutable(); + + Object[] newa = new Object[newSz]; + int at = 0; + + for (int i = 0; i < sz; i++) { + Object one = arr[i]; + if (one != null) { + newa[at] = one; + at++; + } + } + + arr = newa; + if (newSz == 0) { + setImmutable(); + } + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. This method is + * protected so that subclasses may offer a safe type-checked + * public interface to their clients. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + protected final Object get0(int n) { + try { + Object result = arr[n]; + + if (result == null) { + throw new NullPointerException("unset: " + n); + } + + return result; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + return throwIndex(n); + } + } + + /** + * Gets the indicated element, allowing {@code null}s to be + * returned. This method is protected so that subclasses may + * (optionally) offer a safe type-checked public interface to + * their clients. + * + * @param n {@code >= 0, < size();} which element + * @return {@code null-ok;} the indicated element + */ + protected final Object getOrNull0(int n) { + return arr[n]; + } + + /** + * Sets the element at the given index, but without doing any type + * checks on the element. This method is protected so that + * subclasses may offer a safe type-checked public interface to + * their clients. + * + * @param n {@code >= 0, < size();} which element + * @param obj {@code null-ok;} the value to store + */ + protected final void set0(int n, Object obj) { + throwIfImmutable(); + + try { + arr[n] = obj; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throwIndex(n); + } + } + + /** + * Throws the appropriate exception for the given index value. + * + * @param n the index value + * @return never + * @throws IndexOutOfBoundsException always thrown + */ + private Object throwIndex(int n) { + if (n < 0) { + throw new IndexOutOfBoundsException("n < 0"); + } + + throw new IndexOutOfBoundsException("n >= size()"); + } + + /** + * Helper for {@link #toString} and {@link #toHuman}, which both of + * those call to pretty much do everything. + * + * @param prefix {@code null-ok;} prefix for the start of the result + * @param separator {@code null-ok;} separator to insert between each item + * @param suffix {@code null-ok;} suffix for the end of the result + * @param human whether the output is to be human + * @return {@code non-null;} the custom string + */ + private String toString0(String prefix, String separator, String suffix, + boolean human) { + int len = arr.length; + StringBuffer sb = new StringBuffer(len * 10 + 10); + + if (prefix != null) { + sb.append(prefix); + } + + for (int i = 0; i < len; i++) { + if ((i != 0) && (separator != null)) { + sb.append(separator); + } + + if (human) { + sb.append(((ToHuman) arr[i]).toHuman()); + } else { + sb.append(arr[i]); + } + } + + if (suffix != null) { + sb.append(suffix); + } + + return sb.toString(); + } + +} diff --git a/dexlib/src/main/java/com/android/dx/util/Hex.java b/dexlib/src/main/java/com/android/dx/util/Hex.java new file mode 100644 index 000000000..e95669c91 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/Hex.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * Utilities for formatting numbers as hexadecimal. + */ +public final class Hex { + /** + * This class is uninstantiable. + */ + private Hex() { + // This space intentionally left blank. + } + + /** + * Formats a {@code long} as an 8-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u8(long v) { + char[] result = new char[16]; + for (int i = 0; i < 16; i++) { + result[15 - i] = Character.forDigit((int) v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u4(int v) { + char[] result = new char[8]; + for (int i = 0; i < 8; i++) { + result[7 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 3-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u3(int v) { + char[] result = new char[6]; + for (int i = 0; i < 6; i++) { + result[5 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 2-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u2(int v) { + char[] result = new char[4]; + for (int i = 0; i < 4; i++) { + result[3 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as either a 2-byte unsigned hex value + * (if the value is small enough) or a 4-byte unsigned hex value (if + * not). + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u2or4(int v) { + if (v == (char) v) { + return u2(v); + } else { + return u4(v); + } + } + + /** + * Formats an {@code int} as a 1-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u1(int v) { + char[] result = new char[2]; + for (int i = 0; i < 2; i++) { + result[1 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-bit unsigned hex nibble. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String uNibble(int v) { + char[] result = new char[1]; + + result[0] = Character.forDigit(v & 0x0f, 16); + return new String(result); + } + + /** + * Formats a {@code long} as an 8-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s8(long v) { + char[] result = new char[17]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 16; i++) { + result[16 - i] = Character.forDigit((int) v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s4(int v) { + char[] result = new char[9]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 8; i++) { + result[8 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 2-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s2(int v) { + char[] result = new char[5]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 4; i++) { + result[4 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 1-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s1(int v) { + char[] result = new char[3]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 2; i++) { + result[2 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats a hex dump of a portion of a {@code byte[]}. The result + * is always newline-terminated, unless the passed-in length was zero, + * in which case the result is always the empty string ({@code ""}). + * + * @param arr {@code non-null;} array to format + * @param offset {@code >= 0;} offset to the part to dump + * @param length {@code >= 0;} number of bytes to dump + * @param outOffset {@code >= 0;} first output offset to print + * @param bpl {@code >= 0;} number of bytes of output per line + * @param addressLength {@code {2,4,6,8};} number of characters for each address + * header + * @return {@code non-null;} a string of the dump + */ + public static String dump(byte[] arr, int offset, int length, + int outOffset, int bpl, int addressLength) { + int end = offset + length; + + // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0) + if (((offset | length | end) < 0) || (end > arr.length)) { + throw new IndexOutOfBoundsException("arr.length " + + arr.length + "; " + + offset + "..!" + end); + } + + if (outOffset < 0) { + throw new IllegalArgumentException("outOffset < 0"); + } + + if (length == 0) { + return ""; + } + + StringBuffer sb = new StringBuffer(length * 4 + 6); + boolean bol = true; + int col = 0; + + while (length > 0) { + if (col == 0) { + String astr; + switch (addressLength) { + case 2: astr = Hex.u1(outOffset); break; + case 4: astr = Hex.u2(outOffset); break; + case 6: astr = Hex.u3(outOffset); break; + default: astr = Hex.u4(outOffset); break; + } + sb.append(astr); + sb.append(": "); + } else if ((col & 1) == 0) { + sb.append(' '); + } + sb.append(Hex.u1(arr[offset])); + outOffset++; + offset++; + col++; + if (col == bpl) { + sb.append('\n'); + col = 0; + } + length--; + } + + if (col != 0) { + sb.append('\n'); + } + + return sb.toString(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/HexParser.java b/dexlib/src/main/java/com/android/dx/util/HexParser.java new file mode 100644 index 000000000..1b343453d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/HexParser.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * Utilities for parsing hexadecimal text. + */ +public final class HexParser { + /** + * This class is uninstantiable. + */ + private HexParser() { + // This space intentionally left blank. + } + + /** + * Parses the given text as hex, returning a {@code byte[]} + * corresponding to the text. The format is simple: Each line may + * start with a hex offset followed by a colon (which is verified + * and presumably used just as a comment), and then consists of + * hex digits freely interspersed with whitespace. If a pound sign + * is encountered, it and the rest of the line are ignored as a + * comment. If a double quote is encountered, then the ASCII value + * of the subsequent characters is used, until the next double + * quote. Quoted strings may not span multiple lines. + * + * @param src {@code non-null;} the source string + * @return {@code non-null;} the parsed form + */ + public static byte[] parse(String src) { + int len = src.length(); + byte[] result = new byte[len / 2]; + int at = 0; + int outAt = 0; + + while (at < len) { + int nlAt = src.indexOf('\n', at); + if (nlAt < 0) { + nlAt = len; + } + int poundAt = src.indexOf('#', at); + + String line; + if ((poundAt >= 0) && (poundAt < nlAt)) { + line = src.substring(at, poundAt); + } else { + line = src.substring(at, nlAt); + } + at = nlAt + 1; + + int colonAt = line.indexOf(':'); + + atCheck: + if (colonAt != -1) { + int quoteAt = line.indexOf('\"'); + if ((quoteAt != -1) && (quoteAt < colonAt)) { + break atCheck; + } + + String atStr = line.substring(0, colonAt).trim(); + line = line.substring(colonAt + 1); + int alleged = Integer.parseInt(atStr, 16); + if (alleged != outAt) { + throw new RuntimeException("bogus offset marker: " + + atStr); + } + } + + int lineLen = line.length(); + int value = -1; + boolean quoteMode = false; + + for (int i = 0; i < lineLen; i++) { + char c = line.charAt(i); + + if (quoteMode) { + if (c == '\"') { + quoteMode = false; + } else { + result[outAt] = (byte) c; + outAt++; + } + continue; + } + + if (c <= ' ') { + continue; + } + if (c == '\"') { + if (value != -1) { + throw new RuntimeException("spare digit around " + + "offset " + Hex.u4(outAt)); + } + quoteMode = true; + continue; + } + + int digVal = Character.digit(c, 16); + if (digVal == -1) { + throw new RuntimeException("bogus digit character: \"" + + c + "\""); + } + if (value == -1) { + value = digVal; + } else { + result[outAt] = (byte) ((value << 4) | digVal); + outAt++; + value = -1; + } + } + + if (value != -1) { + throw new RuntimeException("spare digit around offset " + + Hex.u4(outAt)); + } + + if (quoteMode) { + throw new RuntimeException("unterminated quote around " + + "offset " + Hex.u4(outAt)); + } + } + + if (outAt < result.length) { + byte[] newr = new byte[outAt]; + System.arraycopy(result, 0, newr, 0, outAt); + result = newr; + } + + return result; + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/IndentingWriter.java b/dexlib/src/main/java/com/android/dx/util/IndentingWriter.java new file mode 100644 index 000000000..3424e37aa --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/IndentingWriter.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import java.io.FilterWriter; +import java.io.IOException; +import java.io.Writer; + +/** + * Writer that wraps another writer and passes width-limited and + * optionally-prefixed output to its subordinate. When lines are + * wrapped they are automatically indented based on the start of the + * line. + */ +public final class IndentingWriter extends FilterWriter { + /** {@code null-ok;} optional prefix for every line */ + private final String prefix; + + /** {@code > 0;} the maximum output width */ + private final int width; + + /** {@code > 0;} the maximum indent */ + private final int maxIndent; + + /** {@code >= 0;} current output column (zero-based) */ + private int column; + + /** whether indent spaces are currently being collected */ + private boolean collectingIndent; + + /** {@code >= 0;} current indent amount */ + private int indent; + + /** + * Constructs an instance. + * + * @param out {@code non-null;} writer to send final output to + * @param width {@code >= 0;} the maximum output width (not including + * {@code prefix}), or {@code 0} for no maximum + * @param prefix {@code non-null;} the prefix for each line + */ + public IndentingWriter(Writer out, int width, String prefix) { + super(out); + + if (out == null) { + throw new NullPointerException("out == null"); + } + + if (width < 0) { + throw new IllegalArgumentException("width < 0"); + } + + if (prefix == null) { + throw new NullPointerException("prefix == null"); + } + + this.width = (width != 0) ? width : Integer.MAX_VALUE; + this.maxIndent = width >> 1; + this.prefix = (prefix.length() == 0) ? null : prefix; + + bol(); + } + + /** + * Constructs a no-prefix instance. + * + * @param out {@code non-null;} writer to send final output to + * @param width {@code >= 0;} the maximum output width (not including + * {@code prefix}), or {@code 0} for no maximum + */ + public IndentingWriter(Writer out, int width) { + this(out, width, ""); + } + + /** {@inheritDoc} */ + @Override + public void write(int c) throws IOException { + synchronized (lock) { + if (collectingIndent) { + if (c == ' ') { + indent++; + if (indent >= maxIndent) { + indent = maxIndent; + collectingIndent = false; + } + } else { + collectingIndent = false; + } + } + + if ((column == width) && (c != '\n')) { + out.write('\n'); + column = 0; + /* + * Note: No else, so this should fall through to the next + * if statement. + */ + } + + if (column == 0) { + if (prefix != null) { + out.write(prefix); + } + + if (!collectingIndent) { + for (int i = 0; i < indent; i++) { + out.write(' '); + } + column = indent; + } + } + + out.write(c); + + if (c == '\n') { + bol(); + } else { + column++; + } + } + } + + /** {@inheritDoc} */ + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + synchronized (lock) { + while (len > 0) { + write(cbuf[off]); + off++; + len--; + } + } + } + + /** {@inheritDoc} */ + @Override + public void write(String str, int off, int len) throws IOException { + synchronized (lock) { + while (len > 0) { + write(str.charAt(off)); + off++; + len--; + } + } + } + + /** + * Indicates that output is at the beginning of a line. + */ + private void bol() { + column = 0; + collectingIndent = (maxIndent != 0); + indent = 0; + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/IntIterator.java b/dexlib/src/main/java/com/android/dx/util/IntIterator.java new file mode 100644 index 000000000..4caa43946 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/IntIterator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * An iterator for a list of ints. + */ +public interface IntIterator { + + /** + * Checks to see if the iterator has a next value. + * + * @return true if next() will succeed + */ + boolean hasNext(); + + /** + * Returns the next value in the iterator. + * + * @return next value + * @throws java.util.NoSuchElementException if no next element exists + */ + int next(); +} diff --git a/dexlib/src/main/java/com/android/dx/util/IntList.java b/dexlib/src/main/java/com/android/dx/util/IntList.java new file mode 100644 index 000000000..be400aa86 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/IntList.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import java.util.Arrays; + +/** + * Simple list of {@code int}s. + */ +public final class IntList extends MutabilityControl { + /** {@code non-null;} immutable, no-element instance */ + public static final IntList EMPTY = new IntList(0); + + /** {@code non-null;} array of elements */ + private int[] values; + + /** {@code >= 0;} current size of the list */ + private int size; + + /** whether the values are currently sorted */ + private boolean sorted; + + static { + EMPTY.setImmutable(); + } + + /** + * Constructs a new immutable instance with the given element. + * + * @param value the sole value in the list + */ + public static IntList makeImmutable(int value) { + IntList result = new IntList(1); + + result.add(value); + result.setImmutable(); + + return result; + } + + /** + * Constructs a new immutable instance with the given elements. + * + * @param value0 the first value in the list + * @param value1 the second value in the list + */ + public static IntList makeImmutable(int value0, int value1) { + IntList result = new IntList(2); + + result.add(value0); + result.add(value1); + result.setImmutable(); + + return result; + } + + /** + * Constructs an empty instance with a default initial capacity. + */ + public IntList() { + this(4); + } + + /** + * Constructs an empty instance. + * + * @param initialCapacity {@code >= 0;} initial capacity of the list + */ + public IntList(int initialCapacity) { + super(true); + + try { + values = new int[initialCapacity]; + } catch (NegativeArraySizeException ex) { + // Translate the exception. + throw new IllegalArgumentException("size < 0"); + } + + size = 0; + sorted = true; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int result = 0; + + for (int i = 0; i < size; i++) { + result = (result * 31) + values[i]; + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (! (other instanceof IntList)) { + return false; + } + + IntList otherList = (IntList) other; + + if (sorted != otherList.sorted) { + return false; + } + + if (size != otherList.size) { + return false; + } + + for (int i = 0; i < size; i++) { + if (values[i] != otherList.values[i]) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(size * 5 + 10); + + sb.append('{'); + + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(values[i]); + } + + sb.append('}'); + + return sb.toString(); + } + + /** + * Gets the number of elements in this list. + */ + public int size() { + return size; + } + + /** + * Gets the indicated value. + * + * @param n {@code >= 0, < size();} which element + * @return the indicated element's value + */ + public int get(int n) { + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } + + try { + return values[n]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate exception. + throw new IndexOutOfBoundsException("n < 0"); + } + } + + /** + * Sets the value at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param value value to store + */ + public void set(int n, int value) { + throwIfImmutable(); + + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } + + try { + values[n] = value; + sorted = false; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + if (n < 0) { + throw new IllegalArgumentException("n < 0"); + } + } + } + + /** + * Adds an element to the end of the list. This will increase the + * list's capacity if necessary. + * + * @param value the value to add + */ + public void add(int value) { + throwIfImmutable(); + + growIfNeeded(); + + values[size++] = value; + + if (sorted && (size > 1)) { + sorted = (value >= values[size - 2]); + } + } + + /** + * Inserts element into specified index, moving elements at and above + * that index up one. May not be used to insert at an index beyond the + * current size (that is, insertion as a last element is legal but + * no further). + * + * @param n {@code >= 0, <=size();} index of where to insert + * @param value value to insert + */ + public void insert(int n, int value) { + if (n > size) { + throw new IndexOutOfBoundsException("n > size()"); + } + + growIfNeeded(); + + System.arraycopy (values, n, values, n+1, size - n); + values[n] = value; + size++; + + sorted = sorted + && (n == 0 || value > values[n-1]) + && (n == (size - 1) || value < values[n+1]); + } + + /** + * Removes an element at a given index, shifting elements at greater + * indicies down one. + * + * @param n {@code >=0, < size();} index of element to remove + */ + public void removeIndex(int n) { + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } + + System.arraycopy (values, n + 1, values, n, size - n - 1); + size--; + + // sort status is unchanged + } + + /** + * Increases size of array if needed + */ + private void growIfNeeded() { + if (size == values.length) { + // Resize. + int[] newv = new int[size * 3 / 2 + 10]; + System.arraycopy(values, 0, newv, 0, size); + values = newv; + } + } + + /** + * Returns the last element in the array without modifying the array + * + * @return last value in the array + * @throws IndexOutOfBoundsException if stack is empty + */ + public int top() { + return get(size - 1); + } + + /** + * Pops an element off the end of the list and decreasing the size by one. + * + * @return value from what was the last element + * @throws IndexOutOfBoundsException if stack is empty + */ + public int pop() { + throwIfImmutable(); + + int result; + + result = get(size-1); + size--; + + return result; + } + + /** + * Pops N elements off the end of the list and decreasing the size by N. + * + * @param n {@code >= 0;} number of elements to remove from end + * @throws IndexOutOfBoundsException if stack is smaller than N + */ + public void pop(int n) { + throwIfImmutable(); + + size -= n; + } + + /** + * Shrinks the size of the list. + * + * @param newSize {@code >= 0;} the new size + */ + public void shrink(int newSize) { + if (newSize < 0) { + throw new IllegalArgumentException("newSize < 0"); + } + + if (newSize > size) { + throw new IllegalArgumentException("newSize > size"); + } + + throwIfImmutable(); + + size = newSize; + } + + /** + * Makes and returns a mutable copy of the list. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public IntList mutableCopy() { + int sz = size; + IntList result = new IntList(sz); + + for (int i = 0; i < sz; i++) { + result.add(values[i]); + } + + return result; + } + + /** + * Sorts the elements in the list in-place. + */ + public void sort() { + throwIfImmutable(); + + if (!sorted) { + Arrays.sort(values, 0, size); + sorted = true; + } + } + + /** + * Returns the index of the given value, or -1 if the value does not + * appear in the list. This will do a binary search if the list is + * sorted or a linear search if not. + * + * @param value value to find + * @return index of value or -1 + */ + public int indexOf(int value) { + int ret = binarysearch(value); + + return ret >= 0 ? ret : -1; + + } + + /** + * Performs a binary search on a sorted list, returning the index of + * the given value if it is present or + * {@code (-(insertion point) - 1)} if the value is not present. + * If the list is not sorted, then reverts to linear search and returns + * {@code -size()} if the element is not found. + * + * @param value value to find + * @return index of value or {@code (-(insertion point) - 1)} if the + * value is not present + */ + public int binarysearch(int value) { + int sz = size; + + if (!sorted) { + // Linear search. + for (int i = 0; i < sz; i++) { + if (values[i] == value) { + return i; + } + } + + return -sz; + } + + /* + * Binary search. This variant does only one value comparison + * per iteration but does one more iteration on average than + * the variant that includes a value equality check per + * iteration. + */ + + int min = -1; + int max = sz; + + while (max > (min + 1)) { + /* + * The guessIdx calculation is equivalent to ((min + max) + * / 2) but won't go wonky when min and max are close to + * Integer.MAX_VALUE. + */ + int guessIdx = min + ((max - min) >> 1); + int guess = values[guessIdx]; + + if (value <= guess) { + max = guessIdx; + } else { + min = guessIdx; + } + } + + if ((max != sz)) { + return (value == values[max]) ? max : (-max - 1); + } else { + return -sz - 1; + } + } + + + /** + * Returns whether or not the given value appears in the list. + * This will do a binary search if the list is sorted or a linear + * search if not. + * + * @see #sort + * + * @param value value to look for + * @return whether the list contains the given value + */ + public boolean contains(int value) { + return indexOf(value) >= 0; + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/IntSet.java b/dexlib/src/main/java/com/android/dx/util/IntSet.java new file mode 100644 index 000000000..33b6bdd48 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/IntSet.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * A set of integers + */ +public interface IntSet { + + /** + * Adds an int to a set + * + * @param value int to add + */ + void add(int value); + + /** + * Removes an int from a set. + * + * @param value int to remove + */ + void remove(int value); + + /** + * Checks to see if a value is in the set + * + * @param value int to check + * @return true if in set + */ + boolean has(int value); + + /** + * Merges {@code other} into this set, so this set becomes the + * union of the two. + * + * @param other {@code non-null;} other set to merge with. + */ + void merge(IntSet other); + + /** + * Returns the count of unique elements in this set. + * + * @return {@code > = 0;} count of unique elements + */ + int elements(); + + /** + * Iterates the set + * + * @return {@code non-null;} a set iterator + */ + IntIterator iterator(); +} diff --git a/dexlib/src/main/java/com/android/dx/util/LabeledItem.java b/dexlib/src/main/java/com/android/dx/util/LabeledItem.java new file mode 100644 index 000000000..b4856cfc9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/LabeledItem.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * An item that has an integer label. + */ +public interface LabeledItem { + + /* + * Gets the label of this block. + * + * @return {@code >= 0;} the label + */ + public int getLabel(); +} diff --git a/dexlib/src/main/java/com/android/dx/util/LabeledList.java b/dexlib/src/main/java/com/android/dx/util/LabeledList.java new file mode 100644 index 000000000..d270a1336 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/LabeledList.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import java.util.Arrays; + +/** + * A list of labeled items, allowing easy lookup by label. + */ +public class LabeledList extends FixedSizeList { + /** + * Sparse array indexed by label to FixedSizeList index; + * {@code -1} for an invalid label. + */ + private final IntList labelToIndex; + + /** {@inheritDoc} */ + public LabeledList(int size) { + super(size); + + labelToIndex = new IntList(size); + } + + /** + * Constructs a new instance that is a copy of the old instance. + * + * @param old instance to copy + */ + public LabeledList(LabeledList old) { + super(old.size()); + labelToIndex = old.labelToIndex.mutableCopy(); + + int sz = old.size(); + + for (int i = 0; i < sz; i++) { + Object one = old.get0(i); + if (one != null) { + set0(i, one); + } + } + } + + /** + * Gets the maximum label (exclusive) of any block added to this instance. + * + * @return {@code >= 0;} the maximum label + */ + public final int getMaxLabel() { + int sz = labelToIndex.size(); + + // Gobble any deleted labels that may be at the end. + int i; + for (i = sz - 1; (i >= 0) && (labelToIndex.get(i) < 0); i--) + /*empty*/ ; + + int newSize = i + 1; + + labelToIndex.shrink(newSize); + + return newSize; + } + + /** + * Removes a label from the label-to-index mapping. + * + * @param oldLabel label to remove + */ + private void removeLabel(int oldLabel) { + labelToIndex.set(oldLabel, -1); + } + + /** + * Adds a label and index to the label-to-index mapping. + * + * @param label new label + * @param index index of block. + */ + private void addLabelIndex(int label, int index) { + int origSz = labelToIndex.size(); + + for (int i = 0; i <= (label - origSz); i++) { + labelToIndex.add(-1); + } + + labelToIndex.set(label, index); + } + + /** + * Gets the index of the first item in the list with the given + * label, if any. + * + * @param label {@code >= 0;} the label to look for + * @return {@code >= -1;} the index of the so-labelled item, or {@code -1} + * if none is found + */ + public final int indexOfLabel(int label) { + if (label >= labelToIndex.size()) { + return -1; + } else { + return labelToIndex.get(label); + } + } + + /** + * Gets an array containing all of the labels used in this instance, + * in order. The returned array is freshly-allocated and so may be + * modified safely by the caller without impacting this instance. + * + * @return {@code non-null;} ordered array of labels + * @throws NullPointerException thrown if there are any {@code null} + * items in this instance + */ + public final int[] getLabelsInOrder() { + int sz = size(); + int[] result = new int[sz]; + + for (int i = 0; i < sz; i++) { + LabeledItem li = (LabeledItem) get0(i); + if (li == null) { + throw new NullPointerException("null at index " + i); + } + result[i] = li.getLabel(); + } + + Arrays.sort(result); + return result; + } + + /** {@inheritDoc} */ + @Override + public void shrinkToFit() { + super.shrinkToFit(); + + rebuildLabelToIndex(); + } + + /** + * Rebuilds the label-to-index mapping after a {@code shrinkToFit()}. + * Note: This assumes that the labels that are in the list are the + * same, although the indicies may have changed. + */ + private void rebuildLabelToIndex() { + int szItems = size(); + + for (int i = 0; i < szItems; i++) { + LabeledItem li = (LabeledItem) get0(i); + + if (li != null) { + labelToIndex.set(li.getLabel(), i); + } + } + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param item {@code null-ok;} the value to store + */ + protected void set(int n, LabeledItem item) { + LabeledItem old = (LabeledItem) getOrNull0(n); + + set0(n, item); + + if (old != null) { + removeLabel(old.getLabel()); + } + + if (item != null) { + addLabelIndex(item.getLabel(), n); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/ListIntSet.java b/dexlib/src/main/java/com/android/dx/util/ListIntSet.java new file mode 100644 index 000000000..129b3053c --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/ListIntSet.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import java.util.NoSuchElementException; + +/** + * A set of integers, represented by a list + */ +public class ListIntSet implements IntSet { + + /** also accessed in BitIntSet */ + final IntList ints; + + /** + * Constructs an instance + */ + public ListIntSet() { + ints = new IntList(); + ints.sort(); + } + + /** {@inheritDoc} */ + public void add(int value) { + int index = ints.binarysearch(value); + + if (index < 0) { + ints.insert(-(index + 1), value); + } + } + + /** {@inheritDoc} */ + public void remove(int value) { + int index = ints.indexOf(value); + + if (index >= 0) { + ints.removeIndex(index); + } + } + + /** {@inheritDoc} */ + public boolean has(int value) { + return ints.indexOf(value) >= 0; + } + + /** {@inheritDoc} */ + public void merge(IntSet other) { + if (other instanceof ListIntSet) { + ListIntSet o = (ListIntSet) other; + int szThis = ints.size(); + int szOther = o.ints.size(); + + int i = 0; + int j = 0; + + while (j < szOther && i < szThis) { + while (j < szOther && o.ints.get(j) < ints.get(i)) { + add(o.ints.get(j++)); + } + if (j == szOther) { + break; + } + while (i < szThis && o.ints.get(j) >= ints.get(i)) { + i++; + } + } + + while (j < szOther) { + add(o.ints.get(j++)); + } + + ints.sort(); + } else if (other instanceof BitIntSet) { + BitIntSet o = (BitIntSet) other; + + for (int i = 0; i >= 0; i = Bits.findFirst(o.bits, i + 1)) { + ints.add(i); + } + ints.sort(); + } else { + IntIterator iter = other.iterator(); + while (iter.hasNext()) { + add(iter.next()); + } + } + } + + /** {@inheritDoc} */ + public int elements() { + return ints.size(); + } + + /** {@inheritDoc} */ + public IntIterator iterator() { + return new IntIterator() { + private int idx = 0; + + /** {@inheritDoc} */ + public boolean hasNext() { + return idx < ints.size(); + } + + /** {@inheritDoc} */ + public int next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + return ints.get(idx++); + } + }; + } + + /** {@inheritDoc} */ + public String toString() { + return ints.toString(); + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/MutabilityControl.java b/dexlib/src/main/java/com/android/dx/util/MutabilityControl.java new file mode 100644 index 000000000..14e0f2ed6 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/MutabilityControl.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * Very simple base class that implements a flag to control the mutability + * of instances. This class just provides the flag and a utility to check + * and throw the right exception, but it is up to subclasses to place calls + * to the checker in all the right places. + */ +public class MutabilityControl { + /** whether this instance is mutable */ + private boolean mutable; + + /** + * Constructs an instance. It is initially mutable. + */ + public MutabilityControl() { + mutable = true; + } + + /** + * Constructs an instance, explicitly indicating the mutability. + * + * @param mutable {@code true} iff this instance is mutable + */ + public MutabilityControl(boolean mutable) { + this.mutable = mutable; + } + + /** + * Makes this instance immutable. + */ + public void setImmutable() { + mutable = false; + } + + /** + * Checks to see whether or not this instance is immutable. This is the + * same as calling {@code !isMutable()}. + * + * @return {@code true} iff this instance is immutable + */ + public final boolean isImmutable() { + return !mutable; + } + + /** + * Checks to see whether or not this instance is mutable. + * + * @return {@code true} iff this instance is mutable + */ + public final boolean isMutable() { + return mutable; + } + + /** + * Throws {@link MutabilityException} if this instance is + * immutable. + */ + public final void throwIfImmutable() { + if (!mutable) { + throw new MutabilityException("immutable instance"); + } + } + + /** + * Throws {@link MutabilityException} if this instance is mutable. + */ + public final void throwIfMutable() { + if (mutable) { + throw new MutabilityException("mutable instance"); + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/MutabilityException.java b/dexlib/src/main/java/com/android/dx/util/MutabilityException.java new file mode 100644 index 000000000..6e029df0d --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/MutabilityException.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import com.android.dex.util.ExceptionWithContext; + +/** + * Exception due to a mutability problem. + */ +public class MutabilityException + extends ExceptionWithContext { + public MutabilityException(String message) { + super(message); + } + + public MutabilityException(Throwable cause) { + super(cause); + } + + public MutabilityException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/Output.java b/dexlib/src/main/java/com/android/dx/util/Output.java new file mode 100644 index 000000000..8ddec2ed9 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/Output.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import com.android.dex.util.ByteOutput; + +/** + * Interface for a sink for binary output. This is similar to + * {@code java.util.DataOutput}, but no {@code IOExceptions} + * are declared, and multibyte output is defined to be little-endian. + */ +public interface Output extends ByteOutput { + /** + * Gets the current cursor position. This is the same as the number of + * bytes written to this instance. + * + * @return {@code >= 0;} the cursor position + */ + public int getCursor(); + + /** + * Asserts that the cursor is the given value. + * + * @param expectedCursor the expected cursor value + * @throws RuntimeException thrown if {@code getCursor() != + * expectedCursor} + */ + public void assertCursor(int expectedCursor); + + /** + * Writes a {@code byte} to this instance. + * + * @param value the value to write; all but the low 8 bits are ignored + */ + public void writeByte(int value); + + /** + * Writes a {@code short} to this instance. + * + * @param value the value to write; all but the low 16 bits are ignored + */ + public void writeShort(int value); + + /** + * Writes an {@code int} to this instance. + * + * @param value the value to write + */ + public void writeInt(int value); + + /** + * Writes a {@code long} to this instance. + * + * @param value the value to write + */ + public void writeLong(long value); + + /** + * Writes a DWARFv3-style unsigned LEB128 integer. For details, + * see the "Dalvik Executable Format" document or DWARF v3 section + * 7.6. + * + * @param value value to write, treated as an unsigned value + * @return {@code 1..5;} the number of bytes actually written + */ + public int writeUleb128(int value); + + /** + * Writes a DWARFv3-style unsigned LEB128 integer. For details, + * see the "Dalvik Executable Format" document or DWARF v3 section + * 7.6. + * + * @param value value to write + * @return {@code 1..5;} the number of bytes actually written + */ + public int writeSleb128(int value); + + /** + * Writes a {@link ByteArray} to this instance. + * + * @param bytes {@code non-null;} the array to write + */ + public void write(ByteArray bytes); + + /** + * Writes a portion of a {@code byte[]} to this instance. + * + * @param bytes {@code non-null;} the array to write + * @param offset {@code >= 0;} offset into {@code bytes} for the first + * byte to write + * @param length {@code >= 0;} number of bytes to write + */ + public void write(byte[] bytes, int offset, int length); + + /** + * Writes a {@code byte[]} to this instance. This is just + * a convenient shorthand for {@code write(bytes, 0, bytes.length)}. + * + * @param bytes {@code non-null;} the array to write + */ + public void write(byte[] bytes); + + /** + * Writes the given number of {@code 0} bytes. + * + * @param count {@code >= 0;} the number of zeroes to write + */ + public void writeZeroes(int count); + + /** + * Adds extra bytes if necessary (with value {@code 0}) to + * force alignment of the output cursor as given. + * + * @param alignment {@code > 0;} the alignment; must be a power of two + */ + public void alignTo(int alignment); +} diff --git a/dexlib/src/main/java/com/android/dx/util/ToHuman.java b/dexlib/src/main/java/com/android/dx/util/ToHuman.java new file mode 100644 index 000000000..b3a31a5c4 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/ToHuman.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * Simple interface for objects that can return a "human" (as opposed to + * a complete but often hard to read) string form. + */ +public interface ToHuman { + /** + * Return the "human" string form of this instance. This is + * generally less "debuggy" than {@code toString()}. + * + * @return {@code non-null;} the human string form + */ + public String toHuman(); +} diff --git a/dexlib/src/main/java/com/android/dx/util/TwoColumnOutput.java b/dexlib/src/main/java/com/android/dx/util/TwoColumnOutput.java new file mode 100644 index 000000000..ed2ab9f47 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/TwoColumnOutput.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.Writer; + +/** + * Class that takes a combined output destination and provides two + * output writers, one of which ends up writing to the left column and + * one which goes on the right. + */ +public final class TwoColumnOutput { + /** {@code non-null;} underlying writer for final output */ + private final Writer out; + + /** {@code > 0;} the left column width */ + private final int leftWidth; + + /** {@code non-null;} pending left column output */ + private final StringBuffer leftBuf; + + /** {@code non-null;} pending right column output */ + private final StringBuffer rightBuf; + + /** {@code non-null;} left column writer */ + private final IndentingWriter leftColumn; + + /** {@code non-null;} right column writer */ + private final IndentingWriter rightColumn; + + /** + * Turns the given two strings (with widths) and spacer into a formatted + * two-column string. + * + * @param s1 {@code non-null;} first string + * @param width1 {@code > 0;} width of the first column + * @param spacer {@code non-null;} spacer string + * @param s2 {@code non-null;} second string + * @param width2 {@code > 0;} width of the second column + * @return {@code non-null;} an appropriately-formatted string + */ + public static String toString(String s1, int width1, String spacer, + String s2, int width2) { + int len1 = s1.length(); + int len2 = s2.length(); + + StringWriter sw = new StringWriter((len1 + len2) * 3); + TwoColumnOutput twoOut = + new TwoColumnOutput(sw, width1, width2, spacer); + + try { + twoOut.getLeft().write(s1); + twoOut.getRight().write(s2); + } catch (IOException ex) { + throw new RuntimeException("shouldn't happen", ex); + } + + twoOut.flush(); + return sw.toString(); + } + + /** + * Constructs an instance. + * + * @param out {@code non-null;} writer to send final output to + * @param leftWidth {@code > 0;} width of the left column, in characters + * @param rightWidth {@code > 0;} width of the right column, in characters + * @param spacer {@code non-null;} spacer string to sit between the two columns + */ + public TwoColumnOutput(Writer out, int leftWidth, int rightWidth, + String spacer) { + if (out == null) { + throw new NullPointerException("out == null"); + } + + if (leftWidth < 1) { + throw new IllegalArgumentException("leftWidth < 1"); + } + + if (rightWidth < 1) { + throw new IllegalArgumentException("rightWidth < 1"); + } + + if (spacer == null) { + throw new NullPointerException("spacer == null"); + } + + StringWriter leftWriter = new StringWriter(1000); + StringWriter rightWriter = new StringWriter(1000); + + this.out = out; + this.leftWidth = leftWidth; + this.leftBuf = leftWriter.getBuffer(); + this.rightBuf = rightWriter.getBuffer(); + this.leftColumn = new IndentingWriter(leftWriter, leftWidth); + this.rightColumn = + new IndentingWriter(rightWriter, rightWidth, spacer); + } + + /** + * Constructs an instance. + * + * @param out {@code non-null;} stream to send final output to + * @param leftWidth {@code >= 1;} width of the left column, in characters + * @param rightWidth {@code >= 1;} width of the right column, in characters + * @param spacer {@code non-null;} spacer string to sit between the two columns + */ + public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth, + String spacer) { + this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer); + } + + /** + * Gets the writer to use to write to the left column. + * + * @return {@code non-null;} the left column writer + */ + public Writer getLeft() { + return leftColumn; + } + + /** + * Gets the writer to use to write to the right column. + * + * @return {@code non-null;} the right column writer + */ + public Writer getRight() { + return rightColumn; + } + + /** + * Flushes the output. If there are more lines of pending output in one + * column, then the other column will get filled with blank lines. + */ + public void flush() { + try { + appendNewlineIfNecessary(leftBuf, leftColumn); + appendNewlineIfNecessary(rightBuf, rightColumn); + outputFullLines(); + flushLeft(); + flushRight(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Outputs to the final destination as many full line pairs as + * there are in the pending output, removing those lines from + * their respective buffers. This method terminates when at + * least one of the two column buffers is empty. + */ + private void outputFullLines() throws IOException { + for (;;) { + int leftLen = leftBuf.indexOf("\n"); + if (leftLen < 0) { + return; + } + + int rightLen = rightBuf.indexOf("\n"); + if (rightLen < 0) { + return; + } + + if (leftLen != 0) { + out.write(leftBuf.substring(0, leftLen)); + } + + if (rightLen != 0) { + writeSpaces(out, leftWidth - leftLen); + out.write(rightBuf.substring(0, rightLen)); + } + + out.write('\n'); + + leftBuf.delete(0, leftLen + 1); + rightBuf.delete(0, rightLen + 1); + } + } + + /** + * Flushes the left column buffer, printing it and clearing the buffer. + * If the buffer is already empty, this does nothing. + */ + private void flushLeft() throws IOException { + appendNewlineIfNecessary(leftBuf, leftColumn); + + while (leftBuf.length() != 0) { + rightColumn.write('\n'); + outputFullLines(); + } + } + + /** + * Flushes the right column buffer, printing it and clearing the buffer. + * If the buffer is already empty, this does nothing. + */ + private void flushRight() throws IOException { + appendNewlineIfNecessary(rightBuf, rightColumn); + + while (rightBuf.length() != 0) { + leftColumn.write('\n'); + outputFullLines(); + } + } + + /** + * Appends a newline to the given buffer via the given writer, but + * only if it isn't empty and doesn't already end with one. + * + * @param buf {@code non-null;} the buffer in question + * @param out {@code non-null;} the writer to use + */ + private static void appendNewlineIfNecessary(StringBuffer buf, + Writer out) + throws IOException { + int len = buf.length(); + + if ((len != 0) && (buf.charAt(len - 1) != '\n')) { + out.write('\n'); + } + } + + /** + * Writes the given number of spaces to the given writer. + * + * @param out {@code non-null;} where to write + * @param amt {@code >= 0;} the number of spaces to write + */ + private static void writeSpaces(Writer out, int amt) throws IOException { + while (amt > 0) { + out.write(' '); + amt--; + } + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/Warning.java b/dexlib/src/main/java/com/android/dx/util/Warning.java new file mode 100644 index 000000000..3c23c7cdc --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/Warning.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +/** + * Exception which is meant to indicate a non-fatal warning. + */ +public class Warning extends RuntimeException { + /** + * Constructs an instance. + * + * @param message human-oriented message + */ + public Warning(String message) { + super(message); + } +} diff --git a/dexlib/src/main/java/com/android/dx/util/Writers.java b/dexlib/src/main/java/com/android/dx/util/Writers.java new file mode 100644 index 000000000..eba845cc4 --- /dev/null +++ b/dexlib/src/main/java/com/android/dx/util/Writers.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dx.util; + +import java.io.PrintWriter; +import java.io.Writer; + +/** + * Utilities for dealing with {@code Writer}s. + */ +public final class Writers { + /** + * This class is uninstantiable. + */ + private Writers() { + // This space intentionally left blank. + } + + /** + * Makes a {@code PrintWriter} for the given {@code Writer}, + * returning the given writer if it already happens to be the right + * class. + * + * @param writer {@code non-null;} writer to (possibly) wrap + * @return {@code non-null;} an appropriate instance + */ + public static PrintWriter printWriterFor(Writer writer) { + if (writer instanceof PrintWriter) { + return (PrintWriter) writer; + } + + return new PrintWriter(writer); + } +} diff --git a/settings.gradle b/settings.gradle index e7b4def49..38ef9c534 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app', ':dexlib'