aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Gruver <bgruv@google.com>2017-05-04 21:15:00 +0000
committerandroid-build-merger <android-build-merger@google.com>2017-05-04 21:15:00 +0000
commitf9202b3a88389bdd0cc79cdf0c37fa1219574729 (patch)
tree89a4e319452bb5b95e307dfac87fa710e2675c74
parentdb31e77294b8be6b55bbf4dc46abb37e79fd8f4a (diff)
parentd9a6252d372ff9b2aae2052646164e2d240bf408 (diff)
downloadsmali-f9202b3a88389bdd0cc79cdf0c37fa1219574729.tar.gz
Merge remote-tracking branch 'aosp/upstream-master' am: 8764034eaf
am: d9a6252d37 Change-Id: Ie82c86673e9cec310e6ad45e02ea61ceb3d8ff6b
-rw-r--r--README.md2
-rw-r--r--baksmali/build.gradle22
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java4
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java13
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java4
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java8
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java6
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java4
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java8
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java31
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java8
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java14
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java10
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java143
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Baksmali.java (renamed from baksmali/src/main/java/org/jf/baksmali/baksmali.java)113
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java (renamed from baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java)98
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java109
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java150
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java287
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/DumpCommand.java106
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/HelpCommand.java204
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListClassesCommand.java76
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListCommand.java85
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListDependenciesCommand.java118
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListDexCommand.java102
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java120
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListFieldsCommand.java50
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java92
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListMethodsCommand.java50
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListReferencesCommand.java74
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListStringsCommand.java50
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListTypesCommand.java50
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java157
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Main.java126
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/dump.java73
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/main.java626
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java26
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java11
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/DexTest.java2
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java4
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java4
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java32
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java2
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java4
-rw-r--r--baksmali/src/test/resources/InstanceOfTest/InstanceOfTest.smali118
-rw-r--r--baksmali/src/test/resources/InstanceOfTest/classes.dexbin0 -> 708 bytes
-rw-r--r--baksmali/src/test/resources/UninitRefIdentityTest/UninitRefIdentityTest.smali66
-rw-r--r--baksmali/src/test/resources/UninitRefIdentityTest/classes.dexbin552 -> 748 bytes
-rw-r--r--build.gradle19
-rw-r--r--dexlib2/OatVersions.txt68
-rw-r--r--dexlib2/VdexVersions.txt8
-rw-r--r--dexlib2/build.gradle1
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java485
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/Opcode.java6
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java33
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java325
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java139
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPathResolver.java465
-rwxr-xr-x[-rw-r--r--]dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java511
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java180
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java184
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java341
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/RegisterType.java2
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/util/ReflectionUtils.java39
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseFieldReference.java5
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodProtoReference.java5
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodReference.java5
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseStringReference.java2
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/builder/BuilderOffsetInstruction.java14
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java126
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedMethodImplementation.java8
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java34
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java245
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/ZipDexContainer.java201
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java102
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/OdexHeaderItem.java77
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/iface/MultiDexContainer.java77
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/immutable/ImmutableDexFile.java12
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/util/DexUtil.java189
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java323
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/IndexSection.java1
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/MethodSection.java1
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BaseBuilderPool.java42
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationPool.java13
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSet.java2
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSetPool.java10
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassDef.java13
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java12
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderContext.java175
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderFieldPool.java17
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderMethodPool.java21
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderProtoPool.java17
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderStringPool.java4
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypeListPool.java9
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypePool.java14
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/builder/DexBuilder.java207
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationPool.java18
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationSetPool.java7
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseIndexPool.java8
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseNullableOffsetPool.java6
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseOffsetPool.java8
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BasePool.java75
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java68
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/DexPool.java146
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/FieldPool.java13
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/Markable.java37
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/MethodPool.java20
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ProtoPool.java16
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringPool.java5
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringTypeBasePool.java9
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypeListPool.java8
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypePool.java8
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java2
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/DexEntryFinderTest.java231
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java95
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java31
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/analysis/MethodAnalyzerTest.java260
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java4
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/pool/RollbackTest.java106
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java8
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java8
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin53638 -> 52928 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties4
-rwxr-xr-xgradlew51
-rw-r--r--gradlew.bat12
-rw-r--r--smali/build.gradle7
-rw-r--r--smali/src/main/antlr/smaliParser.g4
-rw-r--r--smali/src/main/antlr/smaliTreeWalker.g4
-rw-r--r--smali/src/main/java/org/jf/smali/AssembleCommand.java113
-rw-r--r--smali/src/main/java/org/jf/smali/HelpCommand.java92
-rw-r--r--smali/src/main/java/org/jf/smali/Main.java123
-rw-r--r--smali/src/main/java/org/jf/smali/Smali.java208
-rw-r--r--smali/src/main/java/org/jf/smali/SmaliOptions.java12
-rw-r--r--smali/src/main/java/org/jf/smali/SmaliTestUtils.java12
-rw-r--r--smali/src/main/java/org/jf/smali/main.java491
-rw-r--r--smalidea/build.gradle39
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java19
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/debugging/SmaliPositionManager.java23
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyStringReference.java7
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyValue.java8
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaPackedSwitchPayload.java2
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaSparseSwitchPayload.java2
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliAnnotation.java2
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliField.java2
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliImplementsList.java2
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliInstruction.java2
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliMethod.java2
-rw-r--r--smalidea/src/main/java/org/jf/smalidea/psi/index/SmaliClassFinder.java10
-rw-r--r--smalidea/src/main/resources/META-INF/plugin.xml2
-rw-r--r--util/build.gradle2
-rw-r--r--util/src/main/java/org/jf/util/OldWrappedIndentingWriter.java184
-rw-r--r--util/src/main/java/org/jf/util/PathUtil.java25
-rw-r--r--util/src/main/java/org/jf/util/SmaliHelpFormatter.java47
-rw-r--r--util/src/main/java/org/jf/util/StringWrapper.java93
-rw-r--r--util/src/main/java/org/jf/util/WrappedIndentingWriter.java180
-rw-r--r--util/src/main/java/org/jf/util/jcommander/ColonParameterSplitter.java47
-rw-r--r--util/src/main/java/org/jf/util/jcommander/Command.java72
-rw-r--r--util/src/main/java/org/jf/util/jcommander/ExtendedCommands.java150
-rw-r--r--util/src/main/java/org/jf/util/jcommander/ExtendedParameter.java40
-rw-r--r--util/src/main/java/org/jf/util/jcommander/ExtendedParameters.java43
-rw-r--r--util/src/main/java/org/jf/util/jcommander/HelpFormatter.java316
-rw-r--r--util/src/test/java/org/jf/util/StringWrapperTest.java35
162 files changed, 8528 insertions, 3409 deletions
diff --git a/README.md b/README.md
index ccae135d..a5169b48 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
smali/baksmali is an assembler/disassembler for the dex format used by dalvik, Android's Java VM implementation. The syntax is loosely based on Jasmin's/dedexer's syntax, and supports the full functionality of the dex format (annotations, debug info, line info, etc.)
-Downloads are at https://bitbucket.org/JesusFreke/smali/downloads. If you are interested in submitting a patch, feel free to send me a pull request here.
+Downloads are at https://bitbucket.org/JesusFreke/smali/downloads/. If you are interested in submitting a patch, feel free to send me a pull request here.
See [the wiki](https://github.com/JesusFreke/smali/wiki) for more info/news/release notes/etc.
diff --git a/baksmali/build.gradle b/baksmali/build.gradle
index f3a14b19..aae88a35 100644
--- a/baksmali/build.gradle
+++ b/baksmali/build.gradle
@@ -41,8 +41,8 @@ buildscript {
dependencies {
compile project(':util')
compile project(':dexlib2')
- compile depends.commons_cli
compile depends.guava
+ compile depends.jcommander
testCompile depends.junit
testCompile project(':smali')
@@ -59,7 +59,7 @@ task fatJar(type: Jar) {
classifier = 'fat'
manifest {
- attributes('Main-Class': 'org.jf.baksmali.main')
+ attributes('Main-Class': 'org.jf.baksmali.Main')
}
doLast {
@@ -92,7 +92,9 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
dontobfuscate
dontoptimize
- keep 'public class org.jf.baksmali.main { public static void main(java.lang.String[]); }'
+ keep 'public class org.jf.baksmali.Main { public static void main(java.lang.String[]); }'
+ keep 'public class org.jf.util.jcommander.ColonParameterSplitter'
+ keep 'class com.beust.jcommander.** { *; }'
keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
dontwarn 'com.google.common.**'
@@ -100,3 +102,17 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
}
tasks.getByPath(':release').dependsOn(proguard)
+
+task fastbuild(dependsOn: build) {
+}
+
+task fb(dependsOn: fastbuild) {
+}
+
+tasks.getByPath('javadoc').onlyIf({
+ !gradle.taskGraph.hasTask(fastbuild)
+})
+
+tasks.getByPath('test').onlyIf({
+ !gradle.taskGraph.hasTask(fastbuild)
+}) \ No newline at end of file
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java
index 6c67d4ac..4b545ee6 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/CatchMethodItem.java
@@ -28,7 +28,7 @@
package org.jf.baksmali.Adaptors;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull;
@@ -42,7 +42,7 @@ public class CatchMethodItem extends MethodItem {
private final LabelMethodItem tryEndLabel;
private final LabelMethodItem handlerLabel;
- public CatchMethodItem(@Nonnull baksmaliOptions options, @Nonnull MethodDefinition.LabelCache labelCache,
+ public CatchMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition.LabelCache labelCache,
int codeAddress, @Nullable String exceptionType, int startAddress, int endAddress,
int handlerAddress) {
super(codeAddress);
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java
index 2529af8a..361826da 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java
@@ -28,8 +28,7 @@
package org.jf.baksmali.Adaptors;
-import com.google.common.collect.Lists;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex;
@@ -46,16 +45,16 @@ import java.io.IOException;
import java.util.*;
public class ClassDefinition {
- @Nonnull public final baksmaliOptions options;
+ @Nonnull public final BaksmaliOptions options;
@Nonnull public final ClassDef classDef;
@Nonnull private final HashSet<String> fieldsSetInStaticConstructor;
protected boolean validationErrors;
- public ClassDefinition(@Nonnull baksmaliOptions options, @Nonnull ClassDef classDef) {
+ public ClassDefinition(@Nonnull BaksmaliOptions options, @Nonnull ClassDef classDef) {
this.options = options;
this.classDef = classDef;
- fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor();
+ fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor(classDef);
}
public boolean hadValidationErrors() {
@@ -63,7 +62,7 @@ public class ClassDefinition {
}
@Nonnull
- private HashSet<String> findFieldsSetInStaticConstructor() {
+ private static HashSet<String> findFieldsSetInStaticConstructor(@Nonnull ClassDef classDef) {
HashSet<String> fieldsSetInStaticConstructor = new HashSet<String>();
for (Method method: classDef.getDirectMethods()) {
@@ -166,7 +165,7 @@ public class ClassDefinition {
writer.write("# annotations\n");
String containingClass = null;
- if (options.useImplicitReferences) {
+ if (options.implicitReferences) {
containingClass = classDef.getType();
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java
index aed315d7..26807048 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EndTryLabelMethodItem.java
@@ -28,14 +28,14 @@
package org.jf.baksmali.Adaptors;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import javax.annotation.Nonnull;
public class EndTryLabelMethodItem extends LabelMethodItem {
private int endTryAddress;
- public EndTryLabelMethodItem(@Nonnull baksmaliOptions options, int codeAddress, int endTryAddress) {
+ public EndTryLabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, int endTryAddress) {
super(options, codeAddress, "try_end_");
this.endTryAddress = endTryAddress;
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java
index ae017914..90291b79 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java
@@ -29,7 +29,7 @@
package org.jf.baksmali.Adaptors;
import org.jf.baksmali.Adaptors.EncodedValue.EncodedValueAdaptor;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.iface.Annotation;
import org.jf.dexlib2.iface.Field;
@@ -41,7 +41,7 @@ import java.io.IOException;
import java.util.Collection;
public class FieldDefinition {
- public static void writeTo(baksmaliOptions options, IndentingWriter writer, Field field,
+ public static void writeTo(BaksmaliOptions options, IndentingWriter writer, Field field,
boolean setInStaticConstructor) throws IOException {
EncodedValue initialValue = field.getInitialValue();
int accessFlags = field.getAccessFlags();
@@ -68,7 +68,7 @@ public class FieldDefinition {
writer.write(" = ");
String containingClass = null;
- if (options.useImplicitReferences) {
+ if (options.implicitReferences) {
containingClass = field.getDefiningClass();
}
@@ -82,7 +82,7 @@ public class FieldDefinition {
writer.indent(4);
String containingClass = null;
- if (options.useImplicitReferences) {
+ if (options.implicitReferences) {
containingClass = field.getDefiningClass();
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java
index fe85fe00..d58b2b68 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java
@@ -32,7 +32,7 @@ import org.jf.baksmali.Adaptors.MethodDefinition;
import org.jf.baksmali.Adaptors.MethodDefinition.InvalidSwitchPayload;
import org.jf.baksmali.Adaptors.MethodItem;
import org.jf.baksmali.Renderers.LongRenderer;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.VerificationError;
@@ -67,7 +67,7 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
}
private boolean isAllowedOdex(@Nonnull Opcode opcode) {
- baksmaliOptions options = methodDef.classDef.options;
+ BaksmaliOptions options = methodDef.classDef.options;
if (options.allowOdex) {
return true;
}
@@ -110,7 +110,7 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
if (instruction instanceof ReferenceInstruction) {
ReferenceInstruction referenceInstruction = (ReferenceInstruction)instruction;
String classContext = null;
- if (methodDef.classDef.options.useImplicitReferences) {
+ if (methodDef.classDef.options.implicitReferences) {
classContext = methodDef.method.getDefiningClass();
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java
index 3ffb4bd4..be76edfe 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OffsetInstructionFormatMethodItem.java
@@ -30,7 +30,7 @@ package org.jf.baksmali.Adaptors.Format;
import org.jf.baksmali.Adaptors.LabelMethodItem;
import org.jf.baksmali.Adaptors.MethodDefinition;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.iface.instruction.OffsetInstruction;
import org.jf.util.IndentingWriter;
@@ -41,7 +41,7 @@ import java.io.IOException;
public class OffsetInstructionFormatMethodItem extends InstructionMethodItem<OffsetInstruction> {
protected LabelMethodItem label;
- public OffsetInstructionFormatMethodItem(@Nonnull baksmaliOptions options, @Nonnull MethodDefinition methodDef,
+ public OffsetInstructionFormatMethodItem(@Nonnull BaksmaliOptions options, @Nonnull MethodDefinition methodDef,
int codeAddress, OffsetInstruction instruction) {
super(methodDef, codeAddress, instruction);
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java
index b152bb69..268d643c 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/LabelMethodItem.java
@@ -28,18 +28,18 @@
package org.jf.baksmali.Adaptors;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull;
import java.io.IOException;
public class LabelMethodItem extends MethodItem {
- private final baksmaliOptions options;
+ private final BaksmaliOptions options;
private final String labelPrefix;
private int labelSequence;
- public LabelMethodItem(@Nonnull baksmaliOptions options, int codeAddress, @Nonnull String labelPrefix) {
+ public LabelMethodItem(@Nonnull BaksmaliOptions options, int codeAddress, @Nonnull String labelPrefix) {
super(codeAddress);
this.options = options;
this.labelPrefix = labelPrefix;
@@ -76,7 +76,7 @@ public class LabelMethodItem extends MethodItem {
public boolean writeTo(IndentingWriter writer) throws IOException {
writer.write(':');
writer.write(labelPrefix);
- if (options.useSequentialLabels) {
+ if (options.sequentialLabels) {
writer.printUnsignedLongAsHex(labelSequence);
} else {
writer.printUnsignedLongAsHex(this.getLabelAddress());
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
index ef2110a8..8161fe49 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
@@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.jf.baksmali.Adaptors.Debug.DebugMethodItem;
import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.Format;
import org.jf.dexlib2.Opcode;
@@ -163,7 +163,7 @@ public class MethodDefinition {
}
public static void writeEmptyMethodTo(IndentingWriter writer, Method method,
- baksmaliOptions options) throws IOException {
+ BaksmaliOptions options) throws IOException {
writer.write(".method ");
writeAccessFlags(writer, method.getAccessFlags());
writer.write(method.getName());
@@ -180,7 +180,7 @@ public class MethodDefinition {
writeParameters(writer, method, methodParameters, options);
String containingClass = null;
- if (options.useImplicitReferences) {
+ if (options.implicitReferences) {
containingClass = method.getDefiningClass();
}
AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
@@ -212,7 +212,7 @@ public class MethodDefinition {
writer.write('\n');
writer.indent(4);
- if (classDef.options.useLocalsDirective) {
+ if (classDef.options.localsDirective) {
writer.write(".locals ");
writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount);
} else {
@@ -228,7 +228,7 @@ public class MethodDefinition {
}
String containingClass = null;
- if (classDef.options.useImplicitReferences) {
+ if (classDef.options.implicitReferences) {
containingClass = method.getDefiningClass();
}
AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
@@ -313,18 +313,18 @@ public class MethodDefinition {
private static void writeParameters(IndentingWriter writer, Method method,
List<? extends MethodParameter> parameters,
- baksmaliOptions options) throws IOException {
+ BaksmaliOptions options) throws IOException {
boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags());
int registerNumber = isStatic?0:1;
for (MethodParameter parameter: parameters) {
String parameterType = parameter.getType();
String parameterName = parameter.getName();
Collection<? extends Annotation> annotations = parameter.getAnnotations();
- if ((options.outputDebugInfo && parameterName != null) || annotations.size() != 0) {
+ if ((options.debugInfo && parameterName != null) || annotations.size() != 0) {
writer.write(".param p");
writer.printSignedIntAsDec(registerNumber);
- if (parameterName != null && options.outputDebugInfo) {
+ if (parameterName != null && options.debugInfo) {
writer.write(", ");
ReferenceFormatter.writeStringReference(writer, parameterName);
}
@@ -335,7 +335,7 @@ public class MethodDefinition {
writer.indent(4);
String containingClass = null;
- if (options.useImplicitReferences) {
+ if (options.implicitReferences) {
containingClass = method.getDefiningClass();
}
AnnotationFormatter.writeTo(writer, annotations, containingClass);
@@ -374,11 +374,11 @@ public class MethodDefinition {
}
addTries(methodItems);
- if (classDef.options.outputDebugInfo) {
+ if (classDef.options.debugInfo) {
addDebugInfo(methodItems);
}
- if (classDef.options.useSequentialLabels) {
+ if (classDef.options.sequentialLabels) {
setLabelSequentialNumbers();
}
@@ -415,7 +415,7 @@ public class MethodDefinition {
methodItems.add(new BlankMethodItem(currentCodeAddress));
}
- if (classDef.options.addCodeOffsets) {
+ if (classDef.options.codeOffsets) {
methodItems.add(new MethodItem(currentCodeAddress) {
@Override
@@ -432,7 +432,8 @@ public class MethodDefinition {
});
}
- if (!classDef.options.noAccessorComments && (instruction instanceof ReferenceInstruction)) {
+ if (classDef.options.accessorComments && classDef.options.syntheticAccessorResolver != null &&
+ (instruction instanceof ReferenceInstruction)) {
Opcode opcode = instruction.getOpcode();
if (opcode.referenceType == ReferenceType.METHOD) {
@@ -493,7 +494,7 @@ public class MethodDefinition {
methodItems.add(new BlankMethodItem(currentCodeAddress));
}
- if (classDef.options.addCodeOffsets) {
+ if (classDef.options.codeOffsets) {
methodItems.add(new MethodItem(currentCodeAddress) {
@Override
@@ -597,7 +598,7 @@ public class MethodDefinition {
@Nullable
private String getContainingClassForImplicitReference() {
- if (classDef.options.useImplicitReferences) {
+ if (classDef.options.implicitReferences) {
return classDef.classDef.getType();
}
return null;
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java
index 812a282a..62826b1e 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PostInstructionRegisterInfoMethodItem.java
@@ -28,7 +28,7 @@
package org.jf.baksmali.Adaptors;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.analysis.AnalyzedInstruction;
import org.jf.dexlib2.analysis.RegisterType;
import org.jf.util.IndentingWriter;
@@ -60,12 +60,12 @@ public class PostInstructionRegisterInfoMethodItem extends MethodItem {
int registerCount = analyzedInstruction.getRegisterCount();
BitSet registers = new BitSet(registerCount);
- if ((registerInfo & baksmaliOptions.ALL) != 0) {
+ if ((registerInfo & BaksmaliOptions.ALL) != 0) {
registers.set(0, registerCount);
} else {
- if ((registerInfo & baksmaliOptions.ALLPOST) != 0) {
+ if ((registerInfo & BaksmaliOptions.ALLPOST) != 0) {
registers.set(0, registerCount);
- } else if ((registerInfo & baksmaliOptions.DEST) != 0) {
+ } else if ((registerInfo & BaksmaliOptions.DEST) != 0) {
addDestRegs(registers, registerCount);
}
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java
index f5329388..f934eddb 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/PreInstructionRegisterInfoMethodItem.java
@@ -28,7 +28,7 @@
package org.jf.baksmali.Adaptors;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.analysis.AnalyzedInstruction;
import org.jf.dexlib2.analysis.MethodAnalyzer;
import org.jf.dexlib2.analysis.RegisterType;
@@ -68,29 +68,29 @@ public class PreInstructionRegisterInfoMethodItem extends MethodItem {
BitSet registers = new BitSet(registerCount);
BitSet mergeRegisters = null;
- if ((registerInfo & baksmaliOptions.ALL) != 0) {
+ if ((registerInfo & BaksmaliOptions.ALL) != 0) {
registers.set(0, registerCount);
} else {
- if ((registerInfo & baksmaliOptions.ALLPRE) != 0) {
+ if ((registerInfo & BaksmaliOptions.ALLPRE) != 0) {
registers.set(0, registerCount);
} else {
- if ((registerInfo & baksmaliOptions.ARGS) != 0) {
+ if ((registerInfo & BaksmaliOptions.ARGS) != 0) {
addArgsRegs(registers);
}
- if ((registerInfo & baksmaliOptions.MERGE) != 0) {
+ if ((registerInfo & BaksmaliOptions.MERGE) != 0) {
if (analyzedInstruction.isBeginningInstruction()) {
addParamRegs(registers, registerCount);
}
mergeRegisters = new BitSet(registerCount);
addMergeRegs(mergeRegisters, registerCount);
- } else if ((registerInfo & baksmaliOptions.FULLMERGE) != 0 &&
+ } else if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0 &&
(analyzedInstruction.isBeginningInstruction())) {
addParamRegs(registers, registerCount);
}
}
}
- if ((registerInfo & baksmaliOptions.FULLMERGE) != 0) {
+ if ((registerInfo & BaksmaliOptions.FULLMERGE) != 0) {
if (mergeRegisters == null) {
mergeRegisters = new BitSet(registerCount);
addMergeRegs(mergeRegisters, registerCount);
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java
index bffcb385..3d72f468 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/RegisterFormatter.java
@@ -28,7 +28,7 @@
package org.jf.baksmali.Adaptors;
-import org.jf.baksmali.baksmaliOptions;
+import org.jf.baksmali.BaksmaliOptions;
import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull;
@@ -38,11 +38,11 @@ import java.io.IOException;
* This class contains the logic used for formatting registers
*/
public class RegisterFormatter {
- @Nonnull public final baksmaliOptions options;
+ @Nonnull public final BaksmaliOptions options;
public final int registerCount;
public final int parameterRegisterCount;
- public RegisterFormatter(@Nonnull baksmaliOptions options, int registerCount, int parameterRegisterCount) {
+ public RegisterFormatter(@Nonnull BaksmaliOptions options, int registerCount, int parameterRegisterCount) {
this.options = options;
this.registerCount = registerCount;
this.parameterRegisterCount = parameterRegisterCount;
@@ -58,7 +58,7 @@ public class RegisterFormatter {
* @param lastRegister the last register in the range
*/
public void writeRegisterRange(IndentingWriter writer, int startRegister, int lastRegister) throws IOException {
- if (!options.noParameterRegisters) {
+ if (options.parameterRegisters) {
assert startRegister <= lastRegister;
if (startRegister >= registerCount - parameterRegisterCount) {
@@ -86,7 +86,7 @@ public class RegisterFormatter {
* @param register the register number
*/
public void writeTo(IndentingWriter writer, int register) throws IOException {
- if (!options.noParameterRegisters) {
+ if (options.parameterRegisters) {
if (register >= registerCount - parameterRegisterCount) {
writer.write('p');
writer.printSignedIntAsDec((register - (registerCount - parameterRegisterCount)));
diff --git a/baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java b/baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java
new file mode 100644
index 00000000..20bc45be
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.Parameter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.analysis.ClassPath;
+import org.jf.dexlib2.analysis.ClassPathResolver;
+import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.util.jcommander.ColonParameterSplitter;
+import org.jf.util.jcommander.ExtendedParameter;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import static org.jf.dexlib2.analysis.ClassPath.NOT_ART;
+
+public class AnalysisArguments {
+ @Parameter(names = {"-b", "--bootclasspath", "--bcp"},
+ description = "A colon separated list of the files to include in the bootclasspath when analyzing the " +
+ "dex file. If not specified, baksmali will attempt to choose an " +
+ "appropriate default. When analyzing oat files, this can simply be the path to the device's " +
+ "boot.oat file. A single empty string can be used to specify that an empty bootclasspath should " +
+ "be used. (e.g. --bootclasspath \"\") See baksmali help classpath for more information.",
+ splitter = ColonParameterSplitter.class)
+ @ExtendedParameter(argumentNames = "classpath")
+ public List<String> bootClassPath = null;
+
+ @Parameter(names = {"-c", "--classpath", "--cp"},
+ description = "A colon separated list of additional files to include in the classpath when analyzing the " +
+ "dex file. These will be added to the classpath after any bootclasspath entries.",
+ splitter = ColonParameterSplitter.class)
+ @ExtendedParameter(argumentNames = "classpath")
+ public List<String> classPath = Lists.newArrayList();
+
+ @Parameter(names = {"-d", "--classpath-dir", "--cpd", "--dir"},
+ description = "A directory to search for classpath files. This option can be used multiple times to " +
+ "specify multiple directories to search. They will be searched in the order they are provided.")
+ @ExtendedParameter(argumentNames = "dir")
+ public List<String> classPathDirectories = null;
+
+ public static class CheckPackagePrivateArgument {
+ @Parameter(names = {"--check-package-private-access", "--package-private", "--checkpp", "--pp"},
+ description = "Use the package-private access check when calculating vtable indexes. This is enabled " +
+ "by default for oat files. For odex files, this is only needed for odexes from 4.2.0. It " +
+ "was reverted in 4.2.1.")
+ public boolean checkPackagePrivateAccess = false;
+ }
+
+ @Nonnull
+ public ClassPath loadClassPathForDexFile(@Nonnull File dexFileDir, @Nonnull DexFile dexFile,
+ boolean checkPackagePrivateAccess) throws IOException {
+ return loadClassPathForDexFile(dexFileDir, dexFile, checkPackagePrivateAccess, NOT_ART);
+ }
+
+ @Nonnull
+ public ClassPath loadClassPathForDexFile(@Nonnull File dexFileDir, @Nonnull DexFile dexFile,
+ boolean checkPackagePrivateAccess, int oatVersion)
+ throws IOException {
+ ClassPathResolver resolver;
+
+ // By default, oatVersion should be NOT_ART, and we'll automatically set it if dexFile is an oat file. In some
+ // cases the caller may choose to override the oat version, in which case we should use the given oat version
+ // regardless of the actual version of the oat file
+ if (oatVersion == NOT_ART) {
+ if (dexFile instanceof OatDexFile) {
+ checkPackagePrivateAccess = true;
+ oatVersion = ((OatDexFile)dexFile).getContainer().getOatVersion();
+ }
+ } else {
+ // this should always be true for ART
+ checkPackagePrivateAccess = true;
+ }
+
+ if (classPathDirectories == null || classPathDirectories.size() == 0) {
+ classPathDirectories = Lists.newArrayList(dexFileDir.getPath());
+ }
+
+ List<String> filteredClassPathDirectories = Lists.newArrayList();
+ if (classPathDirectories != null) {
+ for (String dir: classPathDirectories) {
+ File file = new File(dir);
+ if (!file.exists()) {
+ System.err.println(String.format("Warning: directory %s does not exist. Ignoring.", dir));
+ } else if (!file.isDirectory()) {
+ System.err.println(String.format("Warning: %s is not a directory. Ignoring.", dir));
+ } else {
+ filteredClassPathDirectories.add(dir);
+ }
+ }
+ }
+
+ if (bootClassPath == null) {
+ // TODO: we should be able to get the api from the Opcodes object associated with the dexFile..
+ // except that the oat version -> api mapping doesn't fully work yet
+ resolver = new ClassPathResolver(filteredClassPathDirectories, classPath, dexFile);
+ } else if (bootClassPath.size() == 1 && bootClassPath.get(0).length() == 0) {
+ // --bootclasspath "" is a special case, denoting that no bootclasspath should be used
+ resolver = new ClassPathResolver(
+ ImmutableList.<String>of(), ImmutableList.<String>of(), classPath, dexFile);
+ } else {
+ resolver = new ClassPathResolver(filteredClassPathDirectories, bootClassPath, classPath, dexFile);
+ }
+
+ if (oatVersion == 0 && dexFile instanceof OatDexFile) {
+ oatVersion = ((OatDexFile)dexFile).getContainer().getOatVersion();
+ }
+ return new ClassPath(resolver.getResolvedClassProviders(), checkPackagePrivateAccess, oatVersion);
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/Baksmali.java
index 50607340..1c0231b5 100644
--- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Baksmali.java
@@ -28,105 +28,28 @@
package org.jf.baksmali;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import org.jf.baksmali.Adaptors.ClassDefinition;
-import org.jf.dexlib2.analysis.ClassPath;
-import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
-import org.jf.dexlib2.util.SyntheticAccessorResolver;
import org.jf.util.ClassFileNameHandler;
import org.jf.util.IndentingWriter;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
+import javax.annotation.Nullable;
import java.io.*;
+import java.util.HashSet;
import java.util.List;
-import java.util.Map.Entry;
+import java.util.Set;
import java.util.concurrent.*;
-public class baksmali {
-
- public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {
- if (options.registerInfo != 0 || options.deodex || options.normalizeVirtualMethods) {
- try {
- Iterable<String> extraClassPathEntries;
- if (options.extraClassPathEntries != null) {
- extraClassPathEntries = options.extraClassPathEntries;
- } else {
- extraClassPathEntries = ImmutableList.of();
- }
-
- options.classPath = ClassPath.fromClassPath(options.bootClassPathDirs,
- Iterables.concat(options.bootClassPathEntries, extraClassPathEntries), dexFile,
- options.apiLevel, options.checkPackagePrivateAccess, options.experimental);
-
- if (options.customInlineDefinitions != null) {
- options.inlineResolver = new CustomInlineMethodResolver(options.classPath,
- options.customInlineDefinitions);
- }
- } catch (Exception ex) {
- System.err.println("\n\nError occurred while loading boot class path files. Aborting.");
- ex.printStackTrace(System.err);
- return false;
- }
- }
-
- if (options.resourceIdFileEntries != null) {
- class PublicHandler extends DefaultHandler {
- String prefix = null;
- public PublicHandler(String prefix) {
- super();
- this.prefix = prefix;
- }
-
- public void startElement(String uri, String localName,
- String qName, Attributes attr) throws SAXException {
- if (qName.equals("public")) {
- String type = attr.getValue("type");
- String name = attr.getValue("name").replace('.', '_');
- Integer public_key = Integer.decode(attr.getValue("id"));
- String public_val = new StringBuffer()
- .append(prefix)
- .append(".")
- .append(type)
- .append(".")
- .append(name)
- .toString();
- options.resourceIds.put(public_key, public_val);
- }
- }
- };
-
- for (Entry<String,String> entry: options.resourceIdFileEntries.entrySet()) {
- try {
- SAXParser saxp = SAXParserFactory.newInstance().newSAXParser();
- String prefix = entry.getValue();
- saxp.parse(entry.getKey(), new PublicHandler(prefix));
- } catch (ParserConfigurationException e) {
- continue;
- } catch (SAXException e) {
- continue;
- } catch (IOException e) {
- continue;
- }
- }
- }
+public class Baksmali {
+ public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options) {
+ return disassembleDexFile(dexFile, outputDir, jobs, options, null);
+ }
- File outputDirectoryFile = new File(options.outputDirectory);
- if (!outputDirectoryFile.exists()) {
- if (!outputDirectoryFile.mkdirs()) {
- System.err.println("Can't create the output directory " + options.outputDirectory);
- return false;
- }
- }
+ public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options,
+ @Nullable List<String> classes) {
//sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
//name collisions, then we'll use the same name for each class, if the dex file goes through multiple
@@ -134,16 +57,20 @@ public class baksmali {
//may still change of course
List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
- if (!options.noAccessorComments) {
- options.syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile.getOpcodes(), classDefs);
- }
-
- final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");
+ final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDir, ".smali");
- ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
+ ExecutorService executor = Executors.newFixedThreadPool(jobs);
List<Future<Boolean>> tasks = Lists.newArrayList();
+ Set<String> classSet = null;
+ if (classes != null) {
+ classSet = new HashSet<String>(classes);
+ }
+
for (final ClassDef classDef: classDefs) {
+ if (classSet != null && !classSet.contains(classDef.getType())) {
+ continue;
+ }
tasks.add(executor.submit(new Callable<Boolean>() {
@Override public Boolean call() throws Exception {
return disassembleClass(classDef, fileNameHandler, options);
@@ -174,7 +101,7 @@ public class baksmali {
}
private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler,
- baksmaliOptions options) {
+ BaksmaliOptions options) {
/**
* The path for the disassembly file is based on the package name
* The class descriptor will look something like:
diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java
index 32685ddf..7ad51243 100644
--- a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java
+++ b/baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java
@@ -31,19 +31,35 @@
package org.jf.baksmali;
-import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.util.SyntheticAccessorResolver;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
-import javax.annotation.Nullable;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
import java.io.File;
-import java.util.Arrays;
+import java.io.IOException;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
-public class baksmaliOptions {
+public class BaksmaliOptions {
+ public int apiLevel = 15;
+
+ public boolean parameterRegisters = true;
+ public boolean localsDirective = false;
+ public boolean sequentialLabels = false;
+ public boolean debugInfo = true;
+ public boolean codeOffsets = false;
+ public boolean accessorComments = true;
+ public boolean allowOdex = false;
+ public boolean deodex = false;
+ public boolean implicitReferences = false;
+ public boolean normalizeVirtualMethods = false;
+
// register info values
public static final int ALL = 1;
public static final int ALLPRE = 2;
@@ -53,56 +69,40 @@ public class baksmaliOptions {
public static final int MERGE = 32;
public static final int FULLMERGE = 64;
- public int apiLevel = 15;
- public String outputDirectory = "out";
- @Nullable public String dexEntry = null;
- public List<String> bootClassPathDirs = Lists.newArrayList();
-
- public List<String> bootClassPathEntries = Lists.newArrayList();
- public List<String> extraClassPathEntries = Lists.newArrayList();
+ public int registerInfo = 0;
- public Map<String,String> resourceIdFileEntries = new HashMap<String,String>();
public Map<Integer,String> resourceIds = new HashMap<Integer,String>();
-
- public boolean noParameterRegisters = false;
- public boolean useLocalsDirective = false;
- public boolean useSequentialLabels = false;
- public boolean outputDebugInfo = true;
- public boolean addCodeOffsets = false;
- public boolean noAccessorComments = false;
- public boolean allowOdex = false;
- public boolean deodex = false;
- public boolean experimental = false;
- public boolean ignoreErrors = false;
- public boolean checkPackagePrivateAccess = false;
- public boolean useImplicitReferences = false;
- public boolean normalizeVirtualMethods = false;
- public File customInlineDefinitions = null;
public InlineMethodResolver inlineResolver = null;
- public int registerInfo = 0;
public ClassPath classPath = null;
- public int jobs = Runtime.getRuntime().availableProcessors();
- public boolean disassemble = true;
- public boolean dump = false;
- public String dumpFileName = null;
-
public SyntheticAccessorResolver syntheticAccessorResolver = null;
- public void setBootClassPath(String bootClassPath) {
- bootClassPathEntries = Lists.newArrayList(bootClassPath.split(":"));
- }
-
- public void addExtraClassPath(String extraClassPath) {
- if (extraClassPath.startsWith(":")) {
- extraClassPath = extraClassPath.substring(1);
- }
- extraClassPathEntries.addAll(Arrays.asList(extraClassPath.split(":")));
- }
-
- public void setResourceIdFiles(String resourceIdFiles) {
- for (String resourceIdFile: resourceIdFiles.split(":")) {
- String[] entry = resourceIdFile.split("=");
- resourceIdFileEntries.put(entry[1], entry[0]);
+ /**
+ * Load the resource ids from a set of public.xml files.
+ *
+ * @param resourceFiles A map of resource prefixes -> public.xml files
+ */
+ public void loadResourceIds(Map<String, File> resourceFiles) throws SAXException, IOException {
+ for (Map.Entry<String, File> entry: resourceFiles.entrySet()) {
+ try {
+ SAXParser saxp = SAXParserFactory.newInstance().newSAXParser();
+ final String prefix = entry.getKey();
+ saxp.parse(entry.getValue(), new DefaultHandler() {
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes attr) throws SAXException {
+ if (qName.equals("public")) {
+ String resourceType = attr.getValue("type");
+ String resourceName = attr.getValue("name").replace('.', '_');
+ Integer resourceId = Integer.decode(attr.getValue("id"));
+ String qualifiedResourceName =
+ String.format("%s.%s.%s", prefix, resourceType, resourceName);
+ resourceIds.put(resourceId, qualifiedResourceName);
+ }
+ }
+ });
+ } catch (ParserConfigurationException ex) {
+ throw new RuntimeException(ex);
+ }
}
}
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java b/baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java
new file mode 100644
index 00000000..3ded479f
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DeodexCommand.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.ParametersDelegate;
+import org.jf.baksmali.AnalysisArguments.CheckPackagePrivateArgument;
+import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
+import org.jf.dexlib2.analysis.InlineMethodResolver;
+import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@Parameters(commandDescription = "Deodexes an odex/oat file")
+@ExtendedParameters(
+ commandName = "deodex",
+ commandAliases = { "de", "x" })
+public class DeodexCommand extends DisassembleCommand {
+
+ @ParametersDelegate
+ protected CheckPackagePrivateArgument checkPackagePrivateArgument = new CheckPackagePrivateArgument();
+
+ @Parameter(names = {"--inline-table", "--inline", "--it"},
+ description = "Specify a file containing a custom inline method table to use. See the " +
+ "\"deodexerant\" tool in the smali github repository to dump the inline method table from a " +
+ "device that uses dalvik.")
+ @ExtendedParameter(argumentNames = "file")
+ private String inlineTable;
+
+ public DeodexCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ @Override protected BaksmaliOptions getOptions() {
+ BaksmaliOptions options = super.getOptions();
+
+ options.deodex = true;
+
+ if (dexFile instanceof DexBackedOdexFile) {
+ if (inlineTable == null) {
+ options.inlineResolver = InlineMethodResolver.createInlineMethodResolver(
+ ((DexBackedOdexFile)dexFile).getOdexVersion());
+ } else {
+ File inlineTableFile = new File(inlineTable);
+ if (!inlineTableFile.exists()) {
+ System.err.println(String.format("Could not find file: %s", inlineTable));
+ System.exit(-1);
+ }
+ try {
+ options.inlineResolver = new CustomInlineMethodResolver(options.classPath, inlineTableFile);
+ } catch (IOException ex) {
+ System.err.println(String.format("Error while reading file: %s", inlineTableFile));
+ ex.printStackTrace(System.err);
+ System.exit(-1);
+ }
+ }
+ }
+
+ return options;
+ }
+
+ @Override protected boolean shouldCheckPackagePrivateAccess() {
+ return checkPackagePrivateArgument.checkPackagePrivateAccess;
+ }
+
+ @Override protected boolean needsClassPath() {
+ return true;
+ }
+
+ @Override protected boolean showDeodexWarning() {
+ return false;
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java b/baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java
new file mode 100644
index 00000000..c7660cbe
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedParameter;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This class implements common functionality for commands that need to load a dex file based on
+ * command line input
+ */
+public abstract class DexInputCommand extends Command {
+
+ @Parameter(names = {"-a", "--api"},
+ description = "The numeric api level of the file being disassembled.")
+ @ExtendedParameter(argumentNames = "api")
+ public int apiLevel = 15;
+
+ @Parameter(description = "A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
+ "files, you can specify the specific entry to use as if the apk/oat file was a directory. " +
+ "e.g. \"app.apk/classes2.dex\". For more information, see \"baksmali help input\".")
+ @ExtendedParameter(argumentNames = "file")
+ protected List<String> inputList = Lists.newArrayList();
+
+ protected File inputFile;
+ protected String inputEntry;
+ protected DexBackedDexFile dexFile;
+
+ public DexInputCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ /**
+ * Parses a dex file input from the user and loads the given dex file.
+ *
+ * In some cases, the input file can contain multiple dex files. If this is the case, you can refer to a specific
+ * dex file with a slash, followed by the entry name, optionally in quotes.
+ *
+ * If the entry name is enclosed in quotes, then it will strip the first and last quote and look for an entry with
+ * exactly that name. Otherwise, it will perform a partial filename match against the entry to find any candidates.
+ * If there is a single matching candidate, it will be used. Otherwise, an error will be generated.
+ *
+ * For example, to refer to the "/system/framework/framework.jar:classes2.dex" entry within the
+ * "framework/arm/framework.oat" oat file, you could use any of:
+ *
+ * framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
+ * framework/arm/framework.oat/system/framework/framework.jar:classes2.dex
+ * framework/arm/framework.oat/framework/framework.jar:classes2.dex
+ * framework/arm/framework.oat/framework.jar:classes2.dex
+ * framework/arm/framework.oat/classes2.dex
+ *
+ * The last option is the easiest, but only works if the oat file doesn't contain another entry with the
+ * "classes2.dex" name. e.g. "/system/framework/blah.jar:classes2.dex"
+ *
+ * It's technically possible (although unlikely) for an oat file to contain 2 entries like:
+ * /system/framework/framework.jar:classes2.dex
+ * system/framework/framework.jar:classes2.dex
+ *
+ * In this case, the "framework/arm/framework.oat/system/framework/framework.jar:classes2.dex" syntax will generate
+ * an error because both entries match the partial entry name. Instead, you could use the following for the
+ * first and second entry respectively:
+ *
+ * framework/arm/framework.oat/"/system/framework/framework.jar:classes2.dex"
+ * framework/arm/framework.oat/"system/framework/framework.jar:classes2.dex"
+ *
+ * @param input The name of a dex, apk, odex or oat file/entry.
+ */
+ protected void loadDexFile(@Nonnull String input) {
+ File file = new File(input);
+
+ while (file != null && !file.exists()) {
+ file = file.getParentFile();
+ }
+
+ if (file == null || !file.exists() || file.isDirectory()) {
+ System.err.println("Can't find file: " + input);
+ System.exit(1);
+ }
+
+ inputFile = file;
+
+ String dexEntry = null;
+ if (file.getPath().length() < input.length()) {
+ dexEntry = input.substring(file.getPath().length() + 1);
+ }
+
+ if (!Strings.isNullOrEmpty(dexEntry)) {
+ boolean exactMatch = false;
+ if (dexEntry.length() > 2 && dexEntry.charAt(0) == '"' && dexEntry.charAt(dexEntry.length() - 1) == '"') {
+ dexEntry = dexEntry.substring(1, dexEntry.length() - 1);
+ exactMatch = true;
+ }
+
+ inputEntry = dexEntry;
+
+ try {
+ dexFile = DexFileFactory.loadDexEntry(file, dexEntry, exactMatch, Opcodes.forApi(apiLevel));
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ } else {
+ try {
+ dexFile = DexFileFactory.loadDexFile(file, Opcodes.forApi(apiLevel));
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java b/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java
new file mode 100644
index 00000000..2e3eb79e
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.ParametersDelegate;
+import com.beust.jcommander.validators.PositiveInteger;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.jf.dexlib2.util.SyntheticAccessorResolver;
+import org.jf.util.StringWrapper;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+import org.xml.sax.SAXException;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+@Parameters(commandDescription = "Disassembles a dex file.")
+@ExtendedParameters(
+ commandName = "disassemble",
+ commandAliases = { "dis", "d" })
+public class DisassembleCommand extends DexInputCommand {
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information for this command.")
+ private boolean help;
+
+ @ParametersDelegate
+ protected AnalysisArguments analysisArguments = new AnalysisArguments();
+
+ @Parameter(names = {"--debug-info", "--di"}, arity = 1,
+ description = "Whether to include debug information in the output (.local, .param, .line, etc.). True " +
+ "by default, use --debug-info=false to disable.")
+ @ExtendedParameter(argumentNames = "boolean")
+ private boolean debugInfo = true;
+
+ @Parameter(names = {"--code-offsets", "--offsets", "--off"},
+ description = "Add a comment before each instruction with it's code offset within the method.")
+ private boolean codeOffsets = false;
+
+ @Parameter(names = {"--resolve-resources", "--rr"}, arity = 2,
+ description = "This will attempt to find any resource id references within the bytecode and add a " +
+ "comment with the name of the resource being referenced. The parameter accepts 2 values:" +
+ "an arbitrary resource prefix and the path to a public.xml file. For example: " +
+ "--resolve-resources android.R framework/res/values/public.xml. This option can be specified " +
+ "multiple times to provide resources from multiple packages.")
+ @ExtendedParameter(argumentNames = {"resource prefix", "public.xml file"})
+ private List<String> resourceIdFiles = Lists.newArrayList();
+
+ @Parameter(names = {"-j", "--jobs"},
+ description = "The number of threads to use. Defaults to the number of cores available.",
+ validateWith = PositiveInteger.class)
+ @ExtendedParameter(argumentNames = "n")
+ private int jobs = Runtime.getRuntime().availableProcessors();
+
+ @Parameter(names = {"-l", "--use-locals"},
+ description = "When disassembling, output the .locals directive with the number of non-parameter " +
+ "registers instead of the .registers directive with the total number of registers.")
+ private boolean localsDirective = false;
+
+ @Parameter(names = {"--accessor-comments", "--ac"}, arity = 1,
+ description = "Generate helper comments for synthetic accessors. True by default, use " +
+ "--accessor-comments=false to disable.")
+ @ExtendedParameter(argumentNames = "boolean")
+ private boolean accessorComments = true;
+
+ @Parameter(names = {"--normalize-virtual-methods", "--norm", "--nvm"},
+ description = "Normalize virtual method references to use the base class where the method is " +
+ "originally declared.")
+ private boolean normalizeVirtualMethods = false;
+
+ @Parameter(names = {"-o", "--output"},
+ description = "The directory to write the disassembled files to.")
+ @ExtendedParameter(argumentNames = "dir")
+ private String outputDir = "out";
+
+ @Parameter(names = {"--parameter-registers", "--preg", "--pr"}, arity = 1,
+ description = "Use the pNN syntax for registers that refer to a method parameter on method entry. True " +
+ "by default, use --parameter-registers=false to disable.")
+ @ExtendedParameter(argumentNames = "boolean")
+ private boolean parameterRegisters = true;
+
+ @Parameter(names = {"-r", "--register-info"},
+ description = "Add comments before/after each instruction with information about register types. " +
+ "The value is a comma-separated list of any of ALL, ALLPRE, ALLPOST, ARGS, DEST, MERGE and " +
+ "FULLMERGE. See \"baksmali help register-info\" for more information.")
+ @ExtendedParameter(argumentNames = "register info specifier")
+ private List<String> registerInfoTypes = Lists.newArrayList();
+
+ @Parameter(names = {"--sequential-labels", "--seq", "--sl"},
+ description = "Create label names using a sequential numbering scheme per label type, rather than " +
+ "using the bytecode address.")
+ private boolean sequentialLabels = false;
+
+ @Parameter(names = {"--implicit-references", "--implicit", "--ir"},
+ description = "Use implicit method and field references (without the class name) for methods and " +
+ "fields from the current class.")
+ private boolean implicitReferences = false;
+
+ @Parameter(names = "--classes",
+ description = "A comma separated list of classes. Only disassemble these classes")
+ @ExtendedParameter(argumentNames = "classes")
+ private List<String> classes = null;
+
+ public DisassembleCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ public void run() {
+ if (help || inputList == null || inputList.isEmpty()) {
+ usage();
+ return;
+ }
+
+ if (inputList.size() > 1) {
+ System.err.println("Too many files specified");
+ usage();
+ return;
+ }
+
+ String input = inputList.get(0);
+ loadDexFile(input);
+
+ if (showDeodexWarning() && dexFile.hasOdexOpcodes()) {
+ StringWrapper.printWrappedString(System.err,
+ "Warning: You are disassembling an odex/oat file without deodexing it. You won't be able to " +
+ "re-assemble the results unless you deodex it. See \"baksmali help deodex\"");
+ }
+
+ File outputDirectoryFile = new File(outputDir);
+ if (!outputDirectoryFile.exists()) {
+ if (!outputDirectoryFile.mkdirs()) {
+ System.err.println("Can't create the output directory " + outputDir);
+ System.exit(-1);
+ }
+ }
+
+ if (analysisArguments.classPathDirectories == null || analysisArguments.classPathDirectories.isEmpty()) {
+ analysisArguments.classPathDirectories = Lists.newArrayList(inputFile.getAbsoluteFile().getParent());
+ }
+
+ if (!Baksmali.disassembleDexFile(dexFile, outputDirectoryFile, jobs, getOptions(), classes)) {
+ System.exit(-1);
+ }
+ }
+
+ protected boolean needsClassPath() {
+ return !registerInfoTypes.isEmpty() || normalizeVirtualMethods;
+ }
+
+ protected boolean shouldCheckPackagePrivateAccess() {
+ return false;
+ }
+
+ protected boolean showDeodexWarning() {
+ return true;
+ }
+
+ protected BaksmaliOptions getOptions() {
+ if (dexFile == null) {
+ throw new IllegalStateException("You must call loadDexFile first");
+ }
+
+ final BaksmaliOptions options = new BaksmaliOptions();
+
+ if (needsClassPath()) {
+ try {
+ options.classPath = analysisArguments.loadClassPathForDexFile(
+ inputFile.getAbsoluteFile().getParentFile(), dexFile, shouldCheckPackagePrivateAccess());
+ } catch (Exception ex) {
+ System.err.println("\n\nError occurred while loading class path files. Aborting.");
+ ex.printStackTrace(System.err);
+ System.exit(-1);
+ }
+ }
+
+ if (!resourceIdFiles.isEmpty()) {
+ Map<String, File> resourceFiles = Maps.newHashMap();
+
+ assert (resourceIdFiles.size() % 2) == 0;
+ for (int i=0; i<resourceIdFiles.size(); i+=2) {
+ String resourcePrefix = resourceIdFiles.get(i);
+ String publicXml = resourceIdFiles.get(i+1);
+
+ File publicXmlFile = new File(publicXml);
+
+ if (!publicXmlFile.exists()) {
+ System.err.println(String.format("Can't find file: %s", publicXmlFile));
+ System.exit(-1);
+ }
+
+ resourceFiles.put(resourcePrefix, publicXmlFile);
+ }
+
+ try {
+ options.loadResourceIds(resourceFiles);
+ } catch (IOException ex) {
+ System.err.println("Error while loading resource files:");
+ ex.printStackTrace(System.err);
+ System.exit(-1);
+ } catch (SAXException ex) {
+ System.err.println("Error while loading resource files:");
+ ex.printStackTrace(System.err);
+ System.exit(-1);
+ }
+ }
+
+ options.parameterRegisters = parameterRegisters;
+ options.localsDirective = localsDirective;
+ options.sequentialLabels = sequentialLabels;
+ options.debugInfo = debugInfo;
+ options.codeOffsets = codeOffsets;
+ options.accessorComments = accessorComments;
+ options.implicitReferences = implicitReferences;
+ options.normalizeVirtualMethods = normalizeVirtualMethods;
+
+ options.registerInfo = 0;
+
+ for (String registerInfoType: registerInfoTypes) {
+ if (registerInfoType.equalsIgnoreCase("ALL")) {
+ options.registerInfo |= BaksmaliOptions.ALL;
+ } else if (registerInfoType.equalsIgnoreCase("ALLPRE")) {
+ options.registerInfo |= BaksmaliOptions.ALLPRE;
+ } else if (registerInfoType.equalsIgnoreCase("ALLPOST")) {
+ options.registerInfo |= BaksmaliOptions.ALLPOST;
+ } else if (registerInfoType.equalsIgnoreCase("ARGS")) {
+ options.registerInfo |= BaksmaliOptions.ARGS;
+ } else if (registerInfoType.equalsIgnoreCase("DEST")) {
+ options.registerInfo |= BaksmaliOptions.DEST;
+ } else if (registerInfoType.equalsIgnoreCase("MERGE")) {
+ options.registerInfo |= BaksmaliOptions.MERGE;
+ } else if (registerInfoType.equalsIgnoreCase("FULLMERGE")) {
+ options.registerInfo |= BaksmaliOptions.FULLMERGE;
+ } else {
+ System.err.println(String.format("Invalid register info type: %s", registerInfoType));
+ usage();
+ System.exit(-1);
+ }
+
+ if ((options.registerInfo & BaksmaliOptions.FULLMERGE) != 0) {
+ options.registerInfo &= ~BaksmaliOptions.MERGE;
+ }
+ }
+
+ if (accessorComments) {
+ options.syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile.getOpcodes(),
+ dexFile.getClasses());
+ }
+
+ return options;
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java b/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java
new file mode 100644
index 00000000..433be125
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.raw.RawDexFile;
+import org.jf.dexlib2.dexbacked.raw.util.DexAnnotator;
+import org.jf.util.ConsoleUtil;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.*;
+import java.util.List;
+
+@Parameters(commandDescription = "Prints an annotated hex dump for the given dex file")
+@ExtendedParameters(
+ commandName = "dump",
+ commandAliases = "du")
+public class DumpCommand extends DexInputCommand {
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information for this command.")
+ private boolean help;
+
+ public DumpCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ public void run() {
+ if (help || inputList == null || inputList.isEmpty()) {
+ usage();
+ return;
+ }
+
+ if (inputList.size() > 1) {
+ System.err.println("Too many files specified");
+ usage();
+ return;
+ }
+
+ String input = inputList.get(0);
+ loadDexFile(input);
+
+ try {
+ dump(dexFile, System.out);
+ } catch (IOException ex) {
+ System.err.println("There was an error while dumping the dex file");
+ ex.printStackTrace(System.err);
+ }
+ }
+
+ /**
+ * Writes an annotated hex dump of the given dex file to output.
+ *
+ * @param dexFile The dex file to dump
+ * @param output An OutputStream to write the annotated hex dump to. The caller is responsible for closing this
+ * when needed.
+ *
+ * @throws IOException
+ */
+ public static void dump(@Nonnull DexBackedDexFile dexFile, @Nonnull OutputStream output)
+ throws IOException {
+ Writer writer = new BufferedWriter(new OutputStreamWriter(output));
+
+ int consoleWidth = ConsoleUtil.getConsoleWidth();
+ if (consoleWidth <= 0) {
+ consoleWidth = 120;
+ }
+
+ RawDexFile rawDexFile = new RawDexFile(dexFile.getOpcodes(), dexFile);
+ DexAnnotator annotator = new DexAnnotator(rawDexFile, consoleWidth);
+ annotator.writeAnnotations(writer);
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/HelpCommand.java b/baksmali/src/main/java/org/jf/baksmali/HelpCommand.java
new file mode 100644
index 00000000..149ac63d
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/HelpCommand.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.google.common.collect.Lists;
+import org.jf.util.ConsoleUtil;
+import org.jf.util.StringWrapper;
+import org.jf.util.jcommander.*;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Shows usage information")
+@ExtendedParameters(
+ commandName = "help",
+ commandAliases = "h")
+public class HelpCommand extends Command {
+
+ public HelpCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ @Parameter(description = "If specified, show the detailed usage information for the given commands")
+ @ExtendedParameter(argumentNames = "commands")
+ private List<String> commands = Lists.newArrayList();
+
+ public void run() {
+ JCommander parentJc = commandAncestors.get(commandAncestors.size() - 1);
+
+ if (commands == null || commands.isEmpty()) {
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(commandAncestors));
+ } else {
+ boolean printedHelp = false;
+ for (String cmd : commands) {
+ if (cmd.equals("register-info")) {
+ printedHelp = true;
+ String registerInfoHelp = "The --register-info parameter will cause baksmali to generate " +
+ "comments before and after every instruction containing register type " +
+ "information about some subset of registers. This parameter accepts a comma-separated list" +
+ "of values specifying which registers and how much information to include.\n" +
+ " ALL: all pre- and post-instruction registers\n" +
+ " ALLPRE: all pre-instruction registers\n" +
+ " ALLPOST: all post-instruction registers\n" +
+ " ARGS: any pre-instruction registers used as arguments to the instruction\n" +
+ " DEST: the post-instruction register used as the output of the instruction\n" +
+ " MERGE: any pre-instruction register that has been merged from multiple " +
+ "incoming code paths\n" +
+ " FULLMERGE: an extended version of MERGE that also includes a list of all " +
+ "the register types from incoming code paths that were merged";
+
+ Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
+ ConsoleUtil.getConsoleWidth());
+ for (String line : lines) {
+ System.out.println(line);
+ }
+ } else if (cmd.equals("input")) {
+ printedHelp = true;
+ String registerInfoHelp = "Apks and oat files can contain multiple dex files. In order to " +
+ "specify a particular dex file, the basic syntax is to treat the apk/oat file as a " +
+ "directory. For example, to load the \"classes2.dex\" entry from \"app.apk\", you can " +
+ "use \"app.apk/classes2.dex\".\n" +
+ "\n" +
+ "For ease of use, you can also specify a partial path to the dex file to load. For " +
+ "example, to load a entry named \"/system/framework/framework.jar:classes2.dex\" from " +
+ "\"framework.oat\", you can use any of the following:\n" +
+ "\"framework.oat/classes2.dex\"\n" +
+ "\"framework.oat/framework.jar:classes2.dex\"\n" +
+ "\"framework.oat/framework/framework.jar:classes2.dex\"\n" +
+ "\"framework.oat/system/framework/framework.jar:classes2.dex\"\n" +
+ "\n" +
+ "In some rare cases, an oat file could have entries that can't be differentiated with " +
+ "the above syntax. For example \"/blah/blah.dex\" and \"blah/blah.dex\". In this case, " +
+ "the \"blah.oat/blah/blah.dex\" would match both entries and generate an error. To get " +
+ "around this, you can add double quotes around the entry name to specify an exact entry " +
+ "name. E.g. blah.oat/\"/blah/blah.dex\" or blah.oat/\"blah/blah.dex\" respectively.";
+
+ Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
+ ConsoleUtil.getConsoleWidth());
+ for (String line : lines) {
+ System.out.println(line);
+ }
+ } else if (cmd.equals("classpath")) {
+ printedHelp = true;
+ String registerInfoHelp = "When deodexing odex/oat files or when using the --register-info " +
+ "option, baksmali needs to load all classes from the framework files on the device " +
+ "in order to fully understand the class hierarchy. There are several options that " +
+ "control how baksmali finds and loads the classpath entries.\n" +
+ "\n"+
+ "L+ devices (ART):\n" +
+ "When deodexing or disassembling a file from an L+ device using ART, you generally " +
+ "just need to specify the path to the boot.oat file via the --bootclasspath/-b " +
+ "parameter. On pre-N devices, the boot.oat file is self-contained and no other files are " +
+ "needed. In N, boot.oat was split into multiple files. In this case, the other " +
+ "files should be in the same directory as the boot.oat file, but you still only need to " +
+ "specify the boot.oat file in the --bootclasspath/-b option. The other files will be " +
+ "automatically loaded from the same directory.\n" +
+ "\n" +
+ "Pre-L devices (dalvik):\n" +
+ "When deodexing odex files from a pre-L device using dalvik, you " +
+ "generally just need to specify the path to a directory containing the framework files " +
+ "from the device via the --classpath-dir/-d option. odex files contain a list of " +
+ "framework files they depend on and baksmali will search for these dependencies in the " +
+ "directory that you specify.\n" +
+ "\n" +
+ "Dex files don't contain a list of dependencies like odex files, so when disassembling a " +
+ "dex file using the --register-info option, and using the framework files from a " +
+ "pre-L device, baksmali will attempt to use a reasonable default list of classpath files " +
+ "based on the api level set via the -a option. If this default list is incorrect, you " +
+ "can override the classpath using the --bootclasspath/-b option. This option accepts a " +
+ "colon separated list of classpath entries. Each entry can be specified in a few " +
+ "different ways.\n" +
+ " - A simple filename like \"framework.jar\"\n" +
+ " - A device path like \"/system/framework/framework.jar\"\n" +
+ " - A local relative or absolute path like \"/tmp/framework/framework.jar\"\n" +
+ "When using the first or second formats, you should also specify the directory " +
+ "containing the framework files via the --classpath-dir/-d option. When using the third " +
+ "format, this option is not needed.\n" +
+ "It's worth noting that the second format matches the format used by Android for the " +
+ "BOOTCLASSPATH environment variable, so you can simply grab the value of that variable " +
+ "from the device and use it as-is.\n" +
+ "\n" +
+ "Examples:\n" +
+ " For an M device:\n" +
+ " adb pull /system/framework/arm/boot.oat /tmp/boot.oat\n" +
+ " baksmali deodex blah.oat -b /tmp/boot.oat\n" +
+ " For an N+ device:\n" +
+ " adb pull /system/framework/arm /tmp/framework\n" +
+ " baksmali deodex blah.oat -b /tmp/framework/boot.oat\n" +
+ " For a pre-L device:\n" +
+ " adb pull /system/framework /tmp/framework\n" +
+ " baksmali deodex blah.odex -d /tmp/framework\n" +
+ " Using the BOOTCLASSPATH on a pre-L device:\n" +
+ " adb pull /system/framework /tmp/framework\n" +
+ " export BOOTCLASSPATH=`adb shell \"echo \\\\$BOOTCLASPATH\"`\n" +
+ " baksmali disassemble --register-info ARGS,DEST blah.apk -b $BOOTCLASSPATH -d " +
+ "/tmp/framework";
+
+ Iterable<String> lines = StringWrapper.wrapStringOnBreaks(registerInfoHelp,
+ ConsoleUtil.getConsoleWidth());
+ for (String line : lines) {
+ System.out.println(line);
+ }
+ } else {
+ JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
+ if (command == null) {
+ System.err.println("No such command: " + cmd);
+ } else {
+ printedHelp = true;
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
+ }
+ }
+ }
+ if (!printedHelp) {
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(commandAncestors));
+ }
+ }
+ }
+
+ @Parameters(hidden = true)
+ @ExtendedParameters(commandName = "hlep")
+ public static class HlepCommand extends HelpCommand {
+ public HlepCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListClassesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListClassesCommand.java
new file mode 100644
index 00000000..fb172bdd
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListClassesCommand.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the classes in a dex file.")
+@ExtendedParameters(
+ commandName = "classes",
+ commandAliases = { "class", "c" })
+public class ListClassesCommand extends DexInputCommand {
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information")
+ private boolean help;
+
+ public ListClassesCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ @Override public void run() {
+ if (help || inputList == null || inputList.isEmpty()) {
+ usage();
+ return;
+ }
+
+ if (inputList.size() > 1) {
+ System.err.println("Too many files specified");
+ usage();
+ return;
+ }
+
+ String input = inputList.get(0);
+ loadDexFile(input);
+
+ for (ClassDef classDef: dexFile.getClasses()) {
+ System.out.println(classDef.getType());
+ }
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListCommand.java
new file mode 100644
index 00000000..95476208
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListCommand.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import org.jf.baksmali.ListHelpCommand.ListHlepCommand;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedCommands;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists various objects in a dex file.")
+@ExtendedParameters(
+ commandName = "list",
+ commandAliases = "l")
+public class ListCommand extends Command {
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information")
+ private boolean help;
+
+ public ListCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ @Override protected void setupCommand(JCommander jc) {
+ List<JCommander> hierarchy = getCommandHierarchy();
+
+ ExtendedCommands.addExtendedCommand(jc, new ListStringsCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListMethodsCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListFieldsCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListTypesCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListClassesCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListDexCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListVtablesCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListFieldOffsetsCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListDependenciesCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListHelpCommand(hierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListHlepCommand(hierarchy));
+ }
+
+ @Override public void run() {
+ JCommander jc = getJCommander();
+ if (help || jc.getParsedCommand() == null) {
+ usage();
+ return;
+ }
+
+ Command command = (Command)jc.getCommands().get(jc.getParsedCommand()).getObjects().get(0);
+ command.run();
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListDependenciesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListDependenciesCommand.java
new file mode 100644
index 00000000..636a87c5
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListDependenciesCommand.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
+import org.jf.dexlib2.dexbacked.OatFile;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.*;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the stored dependencies in an odex/oat file.")
+@ExtendedParameters(
+ commandName = "dependencies",
+ commandAliases = { "deps", "dep" })
+public class ListDependenciesCommand extends Command {
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information")
+ private boolean help;
+
+ @Parameter(description = "An oat/odex file")
+ @ExtendedParameter(argumentNames = "file")
+ private List<String> inputList = Lists.newArrayList();
+
+ public ListDependenciesCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ @Override public void run() {
+ if (help || inputList == null || inputList.isEmpty()) {
+ usage();
+ return;
+ }
+
+ if (inputList.size() > 1) {
+ System.err.println("Too many files specified");
+ usage();
+ return;
+ }
+
+ String input = inputList.get(0);
+ InputStream inputStream = null;
+ try {
+ inputStream = new BufferedInputStream(new FileInputStream(input));
+ } catch (FileNotFoundException ex) {
+ System.err.println("Could not find file: " + input);
+ System.exit(-1);
+ }
+
+ try {
+ OatFile oatFile = OatFile.fromInputStream(inputStream);
+ for (String entry: oatFile.getBootClassPath()) {
+ System.out.println(entry);
+ }
+ return;
+ } catch (OatFile.NotAnOatFileException ex) {
+ // ignore
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ try {
+ DexBackedOdexFile odexFile = DexBackedOdexFile.fromInputStream(Opcodes.getDefault(), inputStream);
+ for (String entry: odexFile.getDependencies()) {
+ System.out.println(entry);
+ }
+ return;
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ } catch (DexBackedOdexFile.NotAnOdexFile ex) {
+ // handled below
+ } catch (DexBackedDexFile.NotADexFile ex) {
+ // handled below
+ }
+
+ System.err.println(input + " is not an odex or oat file.");
+ System.exit(-1);
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListDexCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListDexCommand.java
new file mode 100644
index 00000000..d5862eb1
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListDexCommand.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.iface.MultiDexContainer;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the dex files in an apk/oat file.")
+@ExtendedParameters(
+ commandName = "dex",
+ commandAliases = "d")
+public class ListDexCommand extends Command {
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information")
+ private boolean help;
+
+ @Parameter(description = "An apk or oat file.")
+ @ExtendedParameter(argumentNames = "file")
+ private List<String> inputList = Lists.newArrayList();
+
+ public ListDexCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ @Override public void run() {
+ if (help || inputList == null || inputList.isEmpty()) {
+ usage();
+ return;
+ }
+
+ if (inputList.size() > 1) {
+ System.err.println("Too many files specified");
+ usage();
+ return;
+ }
+
+ String input = inputList.get(0);
+ File file = new File(input);
+
+ if (!file.exists()) {
+ System.err.println(String.format("Could not find the file: %s", input));
+ System.exit(-1);
+ }
+
+ List<String> entries;
+ try {
+ MultiDexContainer<? extends DexBackedDexFile> container =
+ DexFileFactory.loadDexContainer(file, Opcodes.getDefault());
+ entries = container.getDexEntryNames();
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ for (String entry: entries) {
+ System.out.println(entry);
+ }
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java
new file mode 100644
index 00000000..d6dd6e2b
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.ParametersDelegate;
+import org.jf.dexlib2.analysis.ClassProto;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.reference.FieldReference;
+import org.jf.util.SparseArray;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the instance field offsets for classes in a dex file.")
+@ExtendedParameters(
+ commandName = "fieldoffsets",
+ commandAliases = { "fieldoffset", "fo" })
+public class ListFieldOffsetsCommand extends DexInputCommand {
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information")
+ private boolean help;
+
+ @ParametersDelegate
+ private AnalysisArguments analysisArguments = new AnalysisArguments();
+
+ public ListFieldOffsetsCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ @Override public void run() {
+ if (help || inputList == null || inputList.isEmpty()) {
+ usage();
+ return;
+ }
+
+ if (inputList.size() > 1) {
+ System.err.println("Too many files specified");
+ usage();
+ return;
+ }
+
+ String input = inputList.get(0);
+ loadDexFile(input);
+ BaksmaliOptions options = getOptions();
+
+ try {
+ for (ClassDef classDef: dexFile.getClasses()) {
+ ClassProto classProto = (ClassProto) options.classPath.getClass(classDef);
+ SparseArray<FieldReference> fields = classProto.getInstanceFields();
+ String className = "Class " + classDef.getType() + " : " + fields.size() + " instance fields\n";
+ System.out.write(className.getBytes());
+ for (int i=0;i<fields.size();i++) {
+ String field = fields.keyAt(i) + ":" + fields.valueAt(i).getType() + " " + fields.valueAt(i).getName() + "\n";
+ System.out.write(field.getBytes());
+ }
+ System.out.write("\n".getBytes());
+ }
+ System.out.close();
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Nonnull
+ private BaksmaliOptions getOptions() {
+ if (dexFile == null) {
+ throw new IllegalStateException("You must call loadDexFile first");
+ }
+
+ final BaksmaliOptions options = new BaksmaliOptions();
+
+ options.apiLevel = apiLevel;
+
+ try {
+ options.classPath = analysisArguments.loadClassPathForDexFile(
+ inputFile.getAbsoluteFile().getParentFile(), dexFile, false);
+ } catch (Exception ex) {
+ System.err.println("Error occurred while loading class path files.");
+ ex.printStackTrace(System.err);
+ System.exit(-1);
+ }
+
+ return options;
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListFieldsCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListFieldsCommand.java
new file mode 100644
index 00000000..c4d090dd
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListFieldsCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the fields in a dex file's field table.")
+@ExtendedParameters(
+ commandName = "fields",
+ commandAliases = { "field", "f" })
+public class ListFieldsCommand extends ListReferencesCommand {
+ public ListFieldsCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors, ReferenceType.FIELD);
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java
new file mode 100644
index 00000000..2e642861
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListHelpCommand.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.google.common.collect.Iterables;
+import org.jf.util.ConsoleUtil;
+import org.jf.util.jcommander.*;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Shows usage information")
+@ExtendedParameters(
+ commandName = "help",
+ commandAliases = "h")
+public class ListHelpCommand extends Command {
+
+ @Parameter(description = "If specified, show the detailed usage information for the given commands")
+ @ExtendedParameter(argumentNames = "commands")
+ private List<String> commands;
+
+ public ListHelpCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ public void run() {
+ if (commands == null || commands.isEmpty()) {
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(commandAncestors));
+ } else {
+ boolean printedHelp = false;
+ JCommander parentJc = Iterables.getLast(commandAncestors);
+ for (String cmd : commands) {
+ JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
+ if (command == null) {
+ System.err.println("No such command: " + cmd);
+ } else {
+ printedHelp = true;
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
+ }
+ }
+ if (!printedHelp) {
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(commandAncestors));
+ }
+ }
+ }
+
+ @Parameters(hidden = true)
+ @ExtendedParameters(commandName = "hlep")
+ public static class ListHlepCommand extends ListHelpCommand {
+ public ListHlepCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListMethodsCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListMethodsCommand.java
new file mode 100644
index 00000000..603e7647
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListMethodsCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the methods in a dex file's method table.")
+@ExtendedParameters(
+ commandName = "methods",
+ commandAliases = { "method", "m" })
+public class ListMethodsCommand extends ListReferencesCommand {
+ public ListMethodsCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors, ReferenceType.METHOD);
+ }
+} \ No newline at end of file
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListReferencesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListReferencesCommand.java
new file mode 100644
index 00000000..da9c3e31
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListReferencesCommand.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import org.jf.dexlib2.iface.reference.Reference;
+import org.jf.dexlib2.util.ReferenceUtil;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+public abstract class ListReferencesCommand extends DexInputCommand {
+
+ private final int referenceType;
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information")
+ private boolean help;
+
+ public ListReferencesCommand(@Nonnull List<JCommander> commandAncestors, int referenceType) {
+ super(commandAncestors);
+ this.referenceType = referenceType;
+ }
+
+ @Override public void run() {
+ if (help || inputList == null || inputList.isEmpty()) {
+ usage();
+ return;
+ }
+
+ if (inputList.size() > 1) {
+ System.err.println("Too many files specified");
+ usage();
+ return;
+ }
+
+ String input = inputList.get(0);
+ loadDexFile(input);
+
+ for (Reference reference: dexFile.getReferences(referenceType)) {
+ System.out.println(ReferenceUtil.getReferenceString(reference));
+ }
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListStringsCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListStringsCommand.java
new file mode 100644
index 00000000..8694f911
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListStringsCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the strings in a dex file's string table.")
+@ExtendedParameters(
+ commandName = "strings",
+ commandAliases = { "string", "str", "s" })
+public class ListStringsCommand extends ListReferencesCommand {
+ public ListStringsCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors, ReferenceType.STRING);
+ }
+} \ No newline at end of file
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListTypesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListTypesCommand.java
new file mode 100644
index 00000000..fbff2f29
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListTypesCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.ReferenceType;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the type ids in a dex file's type table.")
+@ExtendedParameters(
+ commandName = "types",
+ commandAliases = { "type", "t" })
+public class ListTypesCommand extends ListReferencesCommand {
+ public ListTypesCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors, ReferenceType.TYPE);
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java b/baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java
new file mode 100644
index 00000000..74435b3d
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.ParametersDelegate;
+import org.jf.baksmali.AnalysisArguments.CheckPackagePrivateArgument;
+import org.jf.dexlib2.AccessFlags;
+import org.jf.dexlib2.analysis.ClassProto;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.Method;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.List;
+
+@Parameters(commandDescription = "Lists the virtual method tables for classes in a dex file.")
+@ExtendedParameters(
+ commandName = "vtables",
+ commandAliases = { "vtable", "v" })
+public class ListVtablesCommand extends DexInputCommand {
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information")
+ private boolean help;
+
+ @ParametersDelegate
+ private AnalysisArguments analysisArguments = new AnalysisArguments();
+
+ @ParametersDelegate
+ private CheckPackagePrivateArgument checkPackagePrivateArgument = new CheckPackagePrivateArgument();
+
+ @Parameter(names = "--classes",
+ description = "A comma separated list of classes. Only print the vtable for these classes")
+ @ExtendedParameter(argumentNames = "classes")
+ private List<String> classes = null;
+
+ @Parameter(names = "--override-oat-version",
+ description = "Uses a classpath for the given oat version, regardless of the actual oat version. This " +
+ "can be used, e.g. to list vtables from a dex file, as if they were in an oat file of the given " +
+ "version.")
+ private int oatVersion = 0;
+
+ public ListVtablesCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ @Override public void run() {
+ if (help || inputList == null || inputList.isEmpty()) {
+ usage();
+ return;
+ }
+
+ if (inputList.size() > 1) {
+ System.err.println("Too many files specified");
+ usage();
+ return;
+ }
+
+ String input = inputList.get(0);
+ loadDexFile(input);
+
+ BaksmaliOptions options = getOptions();
+ if (options == null) {
+ return;
+ }
+
+ try {
+ if (classes != null && !classes.isEmpty()) {
+ for (String cls: classes) {
+ listClassVtable((ClassProto)options.classPath.getClass(cls));
+ }
+ return;
+ }
+
+ for (ClassDef classDef : dexFile.getClasses()) {
+ if (!AccessFlags.INTERFACE.isSet(classDef.getAccessFlags())) {
+ listClassVtable((ClassProto)options.classPath.getClass(classDef));
+ }
+ }
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private void listClassVtable(ClassProto classProto) throws IOException {
+ List<Method> methods = classProto.getVtable();
+ String className = "Class " + classProto.getType() + " extends " + classProto.getSuperclass() +
+ " : " + methods.size() + " methods\n";
+ System.out.write(className.getBytes());
+ for (int i = 0; i < methods.size(); i++) {
+ Method method = methods.get(i);
+
+ String methodString = i + ":" + method.getDefiningClass() + "->" + method.getName() + "(";
+ for (CharSequence parameter : method.getParameterTypes()) {
+ methodString += parameter;
+ }
+ methodString += ")" + method.getReturnType() + "\n";
+ System.out.write(methodString.getBytes());
+ }
+ System.out.write("\n".getBytes());
+ }
+
+ protected BaksmaliOptions getOptions() {
+ if (dexFile == null) {
+ throw new IllegalStateException("You must call loadDexFile first");
+ }
+
+ final BaksmaliOptions options = new BaksmaliOptions();
+
+ options.apiLevel = apiLevel;
+
+ try {
+ options.classPath = analysisArguments.loadClassPathForDexFile(inputFile.getAbsoluteFile().getParentFile(),
+ dexFile, checkPackagePrivateArgument.checkPackagePrivateAccess, oatVersion);
+ } catch (Exception ex) {
+ System.err.println("Error occurred while loading class path files.");
+ ex.printStackTrace(System.err);
+ return null;
+ }
+
+ return options;
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Main.java b/baksmali/src/main/java/org/jf/baksmali/Main.java
new file mode 100644
index 00000000..66d9b4f8
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/Main.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.google.common.collect.Lists;
+import org.jf.baksmali.HelpCommand.HlepCommand;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedCommands;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Properties;
+
+@ExtendedParameters(
+ includeParametersInUsage = true,
+ commandName = "baksmali",
+ postfixDescription = "See baksmali help <command> for more information about a specific command")
+public class Main extends Command {
+ public static final String VERSION = loadVersion();
+
+ @Parameter(names = {"--help", "-h", "-?"}, help = true,
+ description = "Show usage information")
+ private boolean help;
+
+ @Parameter(names = {"--version", "-v"}, help = true,
+ description = "Print the version of baksmali and then exit")
+ public boolean version;
+
+ private JCommander jc;
+
+ public Main() {
+ super(Lists.<JCommander>newArrayList());
+ }
+
+ @Override public void run() {
+ }
+
+ @Override protected JCommander getJCommander() {
+ return jc;
+ }
+
+ public static void main(String[] args) {
+ Main main = new Main();
+
+ JCommander jc = new JCommander(main);
+ main.jc = jc;
+ jc.setProgramName("baksmali");
+ List<JCommander> commandHierarchy = main.getCommandHierarchy();
+
+ ExtendedCommands.addExtendedCommand(jc, new DisassembleCommand(commandHierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new DeodexCommand(commandHierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new DumpCommand(commandHierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new HelpCommand(commandHierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new HlepCommand(commandHierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new ListCommand(commandHierarchy));
+
+ jc.parse(args);
+
+ if (main.version) {
+ version();
+ }
+
+ if (jc.getParsedCommand() == null || main.help) {
+ main.usage();
+ return;
+ }
+
+ Command command = (Command)jc.getCommands().get(jc.getParsedCommand()).getObjects().get(0);
+ command.run();
+ }
+
+ protected static void version() {
+ System.out.println("baksmali " + VERSION + " (http://smali.org)");
+ System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
+ System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
+ System.exit(0);
+ }
+
+ private static String loadVersion() {
+ InputStream propertiesStream = Baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
+ String version = "[unknown version]";
+ if (propertiesStream != null) {
+ Properties properties = new Properties();
+ try {
+ properties.load(propertiesStream);
+ version = properties.getProperty("application.version");
+ } catch (IOException ex) {
+ // ignore
+ }
+ }
+ return version;
+ }
+}
diff --git a/baksmali/src/main/java/org/jf/baksmali/dump.java b/baksmali/src/main/java/org/jf/baksmali/dump.java
deleted file mode 100644
index 79405e59..00000000
--- a/baksmali/src/main/java/org/jf/baksmali/dump.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * [The "BSD licence"]
- * Copyright (c) 2010 Ben Gruver (JesusFreke)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.jf.baksmali;
-
-import org.jf.dexlib2.Opcodes;
-import org.jf.dexlib2.dexbacked.DexBackedDexFile;
-import org.jf.dexlib2.dexbacked.raw.RawDexFile;
-import org.jf.dexlib2.dexbacked.raw.util.DexAnnotator;
-import org.jf.util.ConsoleUtil;
-
-import java.io.BufferedWriter;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.Writer;
-
-public class dump {
- public static void dump(DexBackedDexFile dexFile, String dumpFileName, int apiLevel) throws IOException {
- if (dumpFileName != null) {
- Writer writer = null;
-
- try {
- writer = new BufferedWriter(new FileWriter(dumpFileName));
-
- int consoleWidth = ConsoleUtil.getConsoleWidth();
- if (consoleWidth <= 0) {
- consoleWidth = 120;
- }
-
- RawDexFile rawDexFile = new RawDexFile(Opcodes.forApi(apiLevel), dexFile);
- DexAnnotator annotator = new DexAnnotator(rawDexFile, consoleWidth);
- annotator.writeAnnotations(writer);
- } catch (IOException ex) {
- System.err.println("There was an error while dumping the dex file to " + dumpFileName);
- ex.printStackTrace(System.err);
- } finally {
- if (writer != null) {
- try {
- writer.close();
- } catch (IOException ex) {
- System.err.println("There was an error while closing the dump file " + dumpFileName);
- ex.printStackTrace(System.err);
- }
- }
- }
- }
- }
-}
diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java
deleted file mode 100644
index 86f43d82..00000000
--- a/baksmali/src/main/java/org/jf/baksmali/main.java
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- * [The "BSD licence"]
- * Copyright (c) 2010 Ben Gruver (JesusFreke)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.jf.baksmali;
-
-import com.google.common.collect.Lists;
-import org.apache.commons.cli.*;
-import org.jf.dexlib2.DexFileFactory;
-import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
-import org.jf.dexlib2.analysis.InlineMethodResolver;
-import org.jf.dexlib2.dexbacked.DexBackedDexFile;
-import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
-import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
-import org.jf.dexlib2.iface.DexFile;
-import org.jf.util.ConsoleUtil;
-import org.jf.util.SmaliHelpFormatter;
-
-import javax.annotation.Nonnull;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.Locale;
-import java.util.Properties;
-
-public class main {
-
- public static final String VERSION;
-
- private static final Options basicOptions;
- private static final Options debugOptions;
- private static final Options options;
-
- static {
- options = new Options();
- basicOptions = new Options();
- debugOptions = new Options();
- buildOptions();
-
- InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
- if (templateStream != null) {
- Properties properties = new Properties();
- String version = "(unknown)";
- try {
- properties.load(templateStream);
- version = properties.getProperty("application.version");
- } catch (IOException ex) {
- // ignore
- }
- VERSION = version;
- } else {
- VERSION = "[unknown version]";
- }
- }
-
- /**
- * This class is uninstantiable.
- */
- private main() {
- }
-
- /**
- * A more programmatic-friendly entry point for baksmali
- *
- * @param options a baksmaliOptions object with the options to run baksmali with
- * @param inputDexFile The DexFile to disassemble
- * @return true if disassembly completed with no errors, or false if errors were encountered
- */
- public static boolean run(@Nonnull baksmaliOptions options, @Nonnull DexFile inputDexFile) throws IOException {
- if (options.bootClassPathEntries.isEmpty() &&
- (options.deodex || options.registerInfo != 0 || options.normalizeVirtualMethods)) {
- if (inputDexFile instanceof DexBackedOdexFile) {
- options.bootClassPathEntries = ((DexBackedOdexFile)inputDexFile).getDependencies();
- } else {
- options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel,
- options.experimental);
- }
- }
-
- if (options.customInlineDefinitions == null && inputDexFile instanceof DexBackedOdexFile) {
- options.inlineResolver =
- InlineMethodResolver.createInlineMethodResolver(
- ((DexBackedOdexFile)inputDexFile).getOdexVersion());
- }
-
- boolean errorOccurred = false;
- if (options.disassemble) {
- errorOccurred = !baksmali.disassembleDexFile(inputDexFile, options);
- }
-
- if (options.dump) {
- if (!(inputDexFile instanceof DexBackedDexFile)) {
- throw new IllegalArgumentException("Annotated hex-dumps require a DexBackedDexFile");
- }
- dump.dump((DexBackedDexFile)inputDexFile, options.dumpFileName, options.apiLevel);
- }
-
- return !errorOccurred;
- }
-
- /**
- * Run!
- */
- public static void main(String[] args) throws IOException {
- Locale locale = new Locale("en", "US");
- Locale.setDefault(locale);
-
- CommandLineParser parser = new PosixParser();
- CommandLine commandLine;
-
- try {
- commandLine = parser.parse(options, args);
- } catch (ParseException ex) {
- usage();
- return;
- }
-
- baksmaliOptions options = new baksmaliOptions();
-
- String[] remainingArgs = commandLine.getArgs();
- Option[] clOptions = commandLine.getOptions();
-
- for (int i=0; i<clOptions.length; i++) {
- Option option = clOptions[i];
- String opt = option.getOpt();
-
- switch (opt.charAt(0)) {
- case 'v':
- version();
- return;
- case '?':
- while (++i < clOptions.length) {
- if (clOptions[i].getOpt().charAt(0) == '?') {
- usage(true);
- return;
- }
- }
- usage(false);
- return;
- case 'o':
- options.outputDirectory = commandLine.getOptionValue("o");
- break;
- case 'p':
- options.noParameterRegisters = true;
- break;
- case 'l':
- options.useLocalsDirective = true;
- break;
- case 's':
- options.useSequentialLabels = true;
- break;
- case 'b':
- options.outputDebugInfo = false;
- break;
- case 'd':
- options.bootClassPathDirs.add(option.getValue());
- break;
- case 'f':
- options.addCodeOffsets = true;
- break;
- case 'r':
- String[] values = commandLine.getOptionValues('r');
- int registerInfo = 0;
-
- if (values == null || values.length == 0) {
- registerInfo = baksmaliOptions.ARGS | baksmaliOptions.DEST;
- } else {
- for (String value: values) {
- if (value.equalsIgnoreCase("ALL")) {
- registerInfo |= baksmaliOptions.ALL;
- } else if (value.equalsIgnoreCase("ALLPRE")) {
- registerInfo |= baksmaliOptions.ALLPRE;
- } else if (value.equalsIgnoreCase("ALLPOST")) {
- registerInfo |= baksmaliOptions.ALLPOST;
- } else if (value.equalsIgnoreCase("ARGS")) {
- registerInfo |= baksmaliOptions.ARGS;
- } else if (value.equalsIgnoreCase("DEST")) {
- registerInfo |= baksmaliOptions.DEST;
- } else if (value.equalsIgnoreCase("MERGE")) {
- registerInfo |= baksmaliOptions.MERGE;
- } else if (value.equalsIgnoreCase("FULLMERGE")) {
- registerInfo |= baksmaliOptions.FULLMERGE;
- } else {
- usage();
- return;
- }
- }
-
- if ((registerInfo & baksmaliOptions.FULLMERGE) != 0) {
- registerInfo &= ~baksmaliOptions.MERGE;
- }
- }
- options.registerInfo = registerInfo;
- break;
- case 'c':
- String bcp = commandLine.getOptionValue("c");
- if (bcp != null && bcp.charAt(0) == ':') {
- options.addExtraClassPath(bcp);
- } else {
- options.setBootClassPath(bcp);
- }
- break;
- case 'x':
- options.deodex = true;
- break;
- case 'X':
- options.experimental = true;
- break;
- case 'm':
- options.noAccessorComments = true;
- break;
- case 'a':
- options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
- break;
- case 'j':
- options.jobs = Integer.parseInt(commandLine.getOptionValue("j"));
- break;
- case 'i':
- String rif = commandLine.getOptionValue("i");
- options.setResourceIdFiles(rif);
- break;
- case 't':
- options.useImplicitReferences = true;
- break;
- case 'e':
- options.dexEntry = commandLine.getOptionValue("e");
- break;
- case 'k':
- options.checkPackagePrivateAccess = true;
- break;
- case 'n':
- options.normalizeVirtualMethods = true;
- break;
- case 'N':
- options.disassemble = false;
- break;
- case 'D':
- options.dump = true;
- options.dumpFileName = commandLine.getOptionValue("D");
- break;
- case 'I':
- options.ignoreErrors = true;
- break;
- case 'T':
- options.customInlineDefinitions = new File(commandLine.getOptionValue("T"));
- break;
- default:
- assert false;
- }
- }
-
- if (remainingArgs.length != 1) {
- usage();
- return;
- }
-
- String inputDexPath = remainingArgs[0];
- File dexFileFile = new File(inputDexPath);
- if (!dexFileFile.exists()) {
- System.err.println("Can't find the file " + inputDexPath);
- System.exit(1);
- }
-
- //Read in and parse the dex file
- DexBackedDexFile dexFile = null;
- try {
- dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry, options.apiLevel, options.experimental);
- } catch (MultipleDexFilesException ex) {
- System.err.println(String.format("%s contains multiple dex files. You must specify which one to " +
- "disassemble with the -e option", dexFileFile.getName()));
- System.err.println("Valid entries include:");
-
- for (OatDexFile oatDexFile: ex.oatFile.getDexFiles()) {
- System.err.println(oatDexFile.filename);
- }
- System.exit(1);
- }
-
- if (dexFile.hasOdexOpcodes()) {
- if (!options.deodex) {
- System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
- System.err.println("won't be able to re-assemble the results unless you deodex it with the -x");
- System.err.println("option");
- options.allowOdex = true;
- }
- } else {
- options.deodex = false;
- }
-
- if (options.dump) {
- if (options.dumpFileName == null) {
- options.dumpFileName = inputDexPath + ".dump";
- }
- }
-
- try {
- if (!run(options, dexFile)) {
- System.exit(1);
- }
- } catch (IllegalArgumentException ex) {
- System.err.println(ex.getMessage());
- System.exit(1);
- }
- }
-
- /**
- * Prints the usage message.
- */
- private static void usage(boolean printDebugOptions) {
- SmaliHelpFormatter formatter = new SmaliHelpFormatter();
- int consoleWidth = ConsoleUtil.getConsoleWidth();
- if (consoleWidth <= 0) {
- consoleWidth = 80;
- }
-
- formatter.setWidth(consoleWidth);
-
- formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
- "disassembles and/or dumps a dex file", basicOptions, printDebugOptions?debugOptions:null);
- }
-
- private static void usage() {
- usage(false);
- }
-
- /**
- * Prints the version message.
- */
- protected static void version() {
- System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
- System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
- System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
- System.exit(0);
- }
-
- @SuppressWarnings("AccessStaticViaInstance")
- private static void buildOptions() {
- Option versionOption = OptionBuilder.withLongOpt("version")
- .withDescription("prints the version then exits")
- .create("v");
-
- Option helpOption = OptionBuilder.withLongOpt("help")
- .withDescription("prints the help message then exits. Specify twice for debug options")
- .create("?");
-
- Option outputDirOption = OptionBuilder.withLongOpt("output")
- .withDescription("the directory where the disassembled files will be placed. The default is out")
- .hasArg()
- .withArgName("DIR")
- .create("o");
-
- Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
- .withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method " +
- "parameters")
- .create("p");
-
- Option deodexerantOption = OptionBuilder.withLongOpt("deodex")
- .withDescription("deodex the given odex file. This option is ignored if the input file is not an " +
- "odex file")
- .create("x");
-
- Option experimentalOption = OptionBuilder.withLongOpt("experimental")
- .withDescription("enable experimental opcodes to be disassembled, even if they aren't necessarily supported in the Android runtime yet")
- .create("X");
-
- Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
- .withDescription("output the .locals directive with the number of non-parameter registers, rather" +
- " than the .register directive with the total number of register")
- .create("l");
-
- Option sequentialLabelsOption = OptionBuilder.withLongOpt("sequential-labels")
- .withDescription("create label names using a sequential numbering scheme per label type, rather than " +
- "using the bytecode address")
- .create("s");
-
- Option noDebugInfoOption = OptionBuilder.withLongOpt("no-debug-info")
- .withDescription("don't write out debug info (.local, .param, .line, etc.)")
- .create("b");
-
- Option registerInfoOption = OptionBuilder.withLongOpt("register-info")
- .hasOptionalArgs()
- .withArgName("REGISTER_INFO_TYPES")
- .withValueSeparator(',')
- .withDescription("print the specificed type(s) of register information for each instruction. " +
- "\"ARGS,DEST\" is the default if no types are specified.\nValid values are:\nALL: all " +
- "pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " +
- "post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " +
- "instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " +
- "pre-instruction register has been merged from more than 1 different post-instruction " +
- "register from its predecessors\nFULLMERGE: For each register that would be printed by " +
- "MERGE, also show the incoming register types that were merged")
- .create("r");
-
- Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
- .withDescription("A colon-separated list of bootclasspath jar/oat files to use for analysis. Add an " +
- "initial colon to specify that the jars/oats should be appended to the default bootclasspath " +
- "instead of replacing it")
- .hasOptionalArg()
- .withArgName("BOOTCLASSPATH")
- .create("c");
-
- Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
- .withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
- "directory")
- .hasArg()
- .withArgName("DIR")
- .create("d");
-
- Option codeOffsetOption = OptionBuilder.withLongOpt("code-offsets")
- .withDescription("add comments to the disassembly containing the code offset for each address")
- .create("f");
-
- Option noAccessorCommentsOption = OptionBuilder.withLongOpt("no-accessor-comments")
- .withDescription("don't output helper comments for synthetic accessors")
- .create("m");
-
- Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
- .withDescription("The numeric api-level of the file being disassembled. If not " +
- "specified, it defaults to 15 (ICS).")
- .hasArg()
- .withArgName("API_LEVEL")
- .create("a");
-
- Option jobsOption = OptionBuilder.withLongOpt("jobs")
- .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " +
- "maximum of 6")
- .hasArg()
- .withArgName("NUM_THREADS")
- .create("j");
-
- Option resourceIdFilesOption = OptionBuilder.withLongOpt("resource-id-files")
- .withDescription("the resource ID files to use, for analysis. A colon-separated list of prefix=file " +
- "pairs. For example R=res/values/public.xml:" +
- "android.R=$ANDROID_HOME/platforms/android-19/data/res/values/public.xml")
- .hasArg()
- .withArgName("FILES")
- .create("i");
-
- Option noImplicitReferencesOption = OptionBuilder.withLongOpt("implicit-references")
- .withDescription("Use implicit (type-less) method and field references")
- .create("t");
-
- Option checkPackagePrivateAccessOption = OptionBuilder.withLongOpt("check-package-private-access")
- .withDescription("When deodexing, use the package-private access check when calculating vtable " +
- "indexes. It should only be needed for 4.2.0 odexes. The functionality was reverted for " +
- "4.2.1.")
- .create("k");
-
- Option normalizeVirtualMethods = OptionBuilder.withLongOpt("normalize-virtual-methods")
- .withDescription("Normalize virtual method references to the reference the base method.")
- .create("n");
-
- Option dumpOption = OptionBuilder.withLongOpt("dump-to")
- .withDescription("dumps the given dex file into a single annotated dump file named FILE" +
- " (<dexfile>.dump by default), along with the normal disassembly")
- .hasOptionalArg()
- .withArgName("FILE")
- .create("D");
-
- Option ignoreErrorsOption = OptionBuilder.withLongOpt("ignore-errors")
- .withDescription("ignores any non-fatal errors that occur while disassembling/deodexing," +
- " ignoring the class if needed, and continuing with the next class. The default" +
- " behavior is to stop disassembling and exit once an error is encountered")
- .create("I");
-
- Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
- .withDescription("suppresses the output of the disassembly")
- .create("N");
-
- Option inlineTableOption = OptionBuilder.withLongOpt("inline-table")
- .withDescription("specify a file containing a custom inline method table to use for deodexing")
- .hasArg()
- .withArgName("FILE")
- .create("T");
-
- Option dexEntryOption = OptionBuilder.withLongOpt("dex-file")
- .withDescription("looks for dex file named DEX_FILE, defaults to classes.dex")
- .withArgName("DEX_FILE")
- .hasArg()
- .create("e");
-
- basicOptions.addOption(versionOption);
- basicOptions.addOption(helpOption);
- basicOptions.addOption(outputDirOption);
- basicOptions.addOption(noParameterRegistersOption);
- basicOptions.addOption(deodexerantOption);
- basicOptions.addOption(experimentalOption);
- basicOptions.addOption(useLocalsOption);
- basicOptions.addOption(sequentialLabelsOption);
- basicOptions.addOption(noDebugInfoOption);
- basicOptions.addOption(registerInfoOption);
- basicOptions.addOption(classPathOption);
- basicOptions.addOption(classPathDirOption);
- basicOptions.addOption(codeOffsetOption);
- basicOptions.addOption(noAccessorCommentsOption);
- basicOptions.addOption(apiLevelOption);
- basicOptions.addOption(jobsOption);
- basicOptions.addOption(resourceIdFilesOption);
- basicOptions.addOption(noImplicitReferencesOption);
- basicOptions.addOption(dexEntryOption);
- basicOptions.addOption(checkPackagePrivateAccessOption);
- basicOptions.addOption(normalizeVirtualMethods);
-
- debugOptions.addOption(dumpOption);
- debugOptions.addOption(ignoreErrorsOption);
- debugOptions.addOption(noDisassemblyOption);
- debugOptions.addOption(inlineTableOption);
-
- for (Object option: basicOptions.getOptions()) {
- options.addOption((Option)option);
- }
- for (Object option: debugOptions.getOptions()) {
- options.addOption((Option)option);
- }
- }
-
- @Nonnull
- private static List<String> getDefaultBootClassPathForApi(int apiLevel, boolean experimental) {
- if (apiLevel < 9) {
- return Lists.newArrayList(
- "/system/framework/core.jar",
- "/system/framework/ext.jar",
- "/system/framework/framework.jar",
- "/system/framework/android.policy.jar",
- "/system/framework/services.jar");
- } else if (apiLevel < 12) {
- return Lists.newArrayList(
- "/system/framework/core.jar",
- "/system/framework/bouncycastle.jar",
- "/system/framework/ext.jar",
- "/system/framework/framework.jar",
- "/system/framework/android.policy.jar",
- "/system/framework/services.jar",
- "/system/framework/core-junit.jar");
- } else if (apiLevel < 14) {
- return Lists.newArrayList(
- "/system/framework/core.jar",
- "/system/framework/apache-xml.jar",
- "/system/framework/bouncycastle.jar",
- "/system/framework/ext.jar",
- "/system/framework/framework.jar",
- "/system/framework/android.policy.jar",
- "/system/framework/services.jar",
- "/system/framework/core-junit.jar");
- } else if (apiLevel < 16) {
- return Lists.newArrayList(
- "/system/framework/core.jar",
- "/system/framework/core-junit.jar",
- "/system/framework/bouncycastle.jar",
- "/system/framework/ext.jar",
- "/system/framework/framework.jar",
- "/system/framework/android.policy.jar",
- "/system/framework/services.jar",
- "/system/framework/apache-xml.jar",
- "/system/framework/filterfw.jar");
- } else if (apiLevel < 21) {
- // this is correct as of api 17/4.2.2
- return Lists.newArrayList(
- "/system/framework/core.jar",
- "/system/framework/core-junit.jar",
- "/system/framework/bouncycastle.jar",
- "/system/framework/ext.jar",
- "/system/framework/framework.jar",
- "/system/framework/telephony-common.jar",
- "/system/framework/mms-common.jar",
- "/system/framework/android.policy.jar",
- "/system/framework/services.jar",
- "/system/framework/apache-xml.jar");
- } else if (apiLevel < 26) {
- return Lists.newArrayList(
- "/system/framework/core-libart.jar",
- "/system/framework/conscrypt.jar",
- "/system/framework/okhttp.jar",
- "/system/framework/core-junit.jar",
- "/system/framework/bouncycastle.jar",
- "/system/framework/ext.jar",
- "/system/framework/framework.jar",
- "/system/framework/telephony-common.jar",
- "/system/framework/voip-common.jar",
- "/system/framework/ims-common.jar",
- "/system/framework/mms-common.jar",
- "/system/framework/android.policy.jar",
- "/system/framework/apache-xml.jar");
- } else { // api >= 26
- // TODO: verify, add new ones?
- return Lists.newArrayList(
- "/system/framework/core-libart.jar",
- "/system/framework/conscrypt.jar",
- "/system/framework/okhttp.jar",
- "/system/framework/bouncycastle.jar",
- "/system/framework/ext.jar",
- "/system/framework/framework.jar",
- "/system/framework/telephony-common.jar",
- "/system/framework/voip-common.jar",
- "/system/framework/ims-common.jar",
- "/system/framework/mms-common.jar",
- "/system/framework/android.policy.jar",
- "/system/framework/apache-xml.jar");
- }
- }
-}
diff --git a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
index 2bb04dda..a68038dc 100644
--- a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
@@ -36,7 +36,9 @@ import com.google.common.io.Resources;
import junit.framework.Assert;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.ClassPath;
+import org.jf.dexlib2.analysis.ClassProvider;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.util.IndentingWriter;
@@ -49,6 +51,7 @@ import java.io.IOException;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.net.URL;
+import java.util.ArrayList;
public class AnalysisTest {
@@ -68,6 +71,11 @@ public class AnalysisTest {
}
@Test
+ public void InstanceOfTest() throws IOException, URISyntaxException {
+ runTest("InstanceOfTest", true, true);
+ }
+
+ @Test
public void MultipleStartInstructionsTest() throws IOException, URISyntaxException {
runTest("MultipleStartInstructionsTest", true);
}
@@ -83,16 +91,24 @@ public class AnalysisTest {
}
public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException {
+ runTest(test, registerInfo, false);
+ }
+
+ public void runTest(String test, boolean registerInfo, boolean isArt) throws IOException, URISyntaxException {
String dexFilePath = String.format("%s%sclasses.dex", test, File.separatorChar);
- DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false);
+ DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), Opcodes.getDefault());
- baksmaliOptions options = new baksmaliOptions();
+ BaksmaliOptions options = new BaksmaliOptions();
if (registerInfo) {
- options.registerInfo = baksmaliOptions.ALL | baksmaliOptions.FULLMERGE;
- options.classPath = new ClassPath();
+ options.registerInfo = BaksmaliOptions.ALL | BaksmaliOptions.FULLMERGE;
+ if (isArt) {
+ options.classPath = new ClassPath(new ArrayList<ClassProvider>(), true, 56);
+ } else {
+ options.classPath = new ClassPath();
+ }
}
- options.useImplicitReferences = false;
+ options.implicitReferences = false;
for (ClassDef classDef: dexFile.getClasses()) {
StringWriter stringWriter = new StringWriter();
diff --git a/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java
index 1c570b6c..4dd2ad93 100644
--- a/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java
+++ b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java
@@ -48,10 +48,9 @@ import java.io.StringWriter;
public class BaksmaliTestUtils {
public static void assertSmaliCompiledEquals(String source, String expected,
- baksmaliOptions options, boolean stripComments) throws IOException,
+ BaksmaliOptions options, boolean stripComments) throws IOException,
RecognitionException {
- ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel,
- options.experimental);
+ ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel);
// Remove unnecessary whitespace and optionally strip all comments from smali file
String normalizedActual = getNormalizedSmali(classDef, options, stripComments);
@@ -62,13 +61,13 @@ public class BaksmaliTestUtils {
}
public static void assertSmaliCompiledEquals(String source, String expected,
- baksmaliOptions options) throws IOException, RecognitionException {
+ BaksmaliOptions options) throws IOException, RecognitionException {
assertSmaliCompiledEquals(source, expected, options, false);
}
public static void assertSmaliCompiledEquals(String source, String expected)
throws IOException, RecognitionException {
- baksmaliOptions options = new baksmaliOptions();
+ BaksmaliOptions options = new BaksmaliOptions();
assertSmaliCompiledEquals(source, expected, options);
}
@@ -81,7 +80,7 @@ public class BaksmaliTestUtils {
}
@Nonnull
- public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull baksmaliOptions options,
+ public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull BaksmaliOptions options,
boolean stripComments)
throws IOException {
StringWriter stringWriter = new StringWriter();
diff --git a/baksmali/src/test/java/org/jf/baksmali/DexTest.java b/baksmali/src/test/java/org/jf/baksmali/DexTest.java
index 5a4db658..f9f55622 100644
--- a/baksmali/src/test/java/org/jf/baksmali/DexTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/DexTest.java
@@ -65,7 +65,7 @@ public abstract class DexTest {
}
@Nonnull
- protected DexBackedDexFile getInputDexFile(@Nonnull String testName, @Nonnull baksmaliOptions options) {
+ protected DexBackedDexFile getInputDexFile(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
try {
// Load file from resources as a stream
byte[] inputBytes = BaksmaliTestUtils.readResourceBytesFully(getInputFilename(testName));
diff --git a/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java
index 1a34e8c3..769372eb 100644
--- a/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java
@@ -57,10 +57,10 @@ public class DisassemblyTest extends DexTest {
}
protected void runTest(@Nonnull String testName) {
- runTest(testName, new baksmaliOptions());
+ runTest(testName, new BaksmaliOptions());
}
- protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) {
+ protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
try {
DexBackedDexFile inputDex = getInputDexFile(testName, options);
Assert.assertEquals(1, inputDex.getClassCount());
diff --git a/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java b/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java
index 78fabc0b..ad2aad5b 100644
--- a/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/FieldGapOrderTest.java
@@ -42,7 +42,7 @@ import org.junit.Test;
public class FieldGapOrderTest extends DexTest {
@Test
public void testOldOrder() {
- DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions());
+ DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions());
Assert.assertEquals(3, dexFile.getClasses().size());
ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 66);
@@ -56,7 +56,7 @@ public class FieldGapOrderTest extends DexTest {
@Test
public void testNewOrder() {
- DexFile dexFile = getInputDexFile("FieldGapOrder", new baksmaliOptions());
+ DexFile dexFile = getInputDexFile("FieldGapOrder", new BaksmaliOptions());
Assert.assertEquals(3, dexFile.getClasses().size());
ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), false, 67);
diff --git a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
index 1f2ae5bf..962a6be7 100644
--- a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
@@ -62,8 +62,8 @@ public class ImplicitReferenceTest {
"return-void\n" +
".end method\n";
- baksmaliOptions options = new baksmaliOptions();
- options.useImplicitReferences = true;
+ BaksmaliOptions options = new BaksmaliOptions();
+ options.implicitReferences = true;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@@ -93,8 +93,8 @@ public class ImplicitReferenceTest {
" return-void\n" +
".end method\n";
- baksmaliOptions options = new baksmaliOptions();
- options.useImplicitReferences = false;
+ BaksmaliOptions options = new BaksmaliOptions();
+ options.implicitReferences = false;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@@ -118,8 +118,8 @@ public class ImplicitReferenceTest {
".field public static field3:Ljava/lang/reflect/Method; = I()V\n" +
".field public static field4:Ljava/lang/Class; = I\n";
- baksmaliOptions options = new baksmaliOptions();
- options.useImplicitReferences = true;
+ BaksmaliOptions options = new BaksmaliOptions();
+ options.implicitReferences = true;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@@ -143,8 +143,8 @@ public class ImplicitReferenceTest {
".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
".field public static field4:Ljava/lang/Class; = I\n";
- baksmaliOptions options = new baksmaliOptions();
- options.useImplicitReferences = false;
+ BaksmaliOptions options = new BaksmaliOptions();
+ options.implicitReferences = false;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@@ -174,8 +174,8 @@ public class ImplicitReferenceTest {
" return-void\n" +
".end method\n";
- baksmaliOptions options = new baksmaliOptions();
- options.useImplicitReferences = true;
+ BaksmaliOptions options = new BaksmaliOptions();
+ options.implicitReferences = true;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@@ -205,8 +205,8 @@ public class ImplicitReferenceTest {
" return-void\n" +
".end method\n";
- baksmaliOptions options = new baksmaliOptions();
- options.useImplicitReferences = false;
+ BaksmaliOptions options = new BaksmaliOptions();
+ options.implicitReferences = false;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@@ -228,8 +228,8 @@ public class ImplicitReferenceTest {
".field public static field2:Ljava/lang/reflect/Field; = V:I\n" +
".field public static field3:Ljava/lang/reflect/Field; = I:I\n";
- baksmaliOptions options = new baksmaliOptions();
- options.useImplicitReferences = true;
+ BaksmaliOptions options = new BaksmaliOptions();
+ options.implicitReferences = true;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@@ -251,8 +251,8 @@ public class ImplicitReferenceTest {
".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I\n";
- baksmaliOptions options = new baksmaliOptions();
- options.useImplicitReferences = false;
+ BaksmaliOptions options = new BaksmaliOptions();
+ options.implicitReferences = false;
BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
diff --git a/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java b/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java
index d85d7913..f1ade1e9 100644
--- a/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/InterfaceOrderTest.java
@@ -36,6 +36,6 @@ import org.junit.Test;
public class InterfaceOrderTest extends IdenticalRoundtripTest {
@Test
public void testInterfaceOrder() {
- runTest("InterfaceOrder", new baksmaliOptions());
+ runTest("InterfaceOrder", new BaksmaliOptions());
}
}
diff --git a/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java
index c9ff2d4d..81e98a30 100644
--- a/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java
@@ -69,10 +69,10 @@ public abstract class RoundtripTest {
}
protected void runTest(@Nonnull String testName) {
- runTest(testName, new baksmaliOptions());
+ runTest(testName, new BaksmaliOptions());
}
- protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) {
+ protected void runTest(@Nonnull String testName, @Nonnull BaksmaliOptions options) {
try {
// Load file from resources as a stream
String inputFilename = getInputFilename(testName);
diff --git a/baksmali/src/test/resources/InstanceOfTest/InstanceOfTest.smali b/baksmali/src/test/resources/InstanceOfTest/InstanceOfTest.smali
new file mode 100644
index 00000000..8e3337af
--- /dev/null
+++ b/baksmali/src/test/resources/InstanceOfTest/InstanceOfTest.smali
@@ -0,0 +1,118 @@
+.class public LInstanceOfTest;
+.super Ljava/lang/Object;
+
+
+# virtual methods
+.method public testInstanceOfEqz(Ljava/lang/Object;)I
+ .registers 3
+
+ #v0=(Uninit);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+ instance-of v0, p1, Ljava/lang/String;
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+ if-eqz v0, :cond_9
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Unknown);
+
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+ invoke-virtual {p1}, Ljava/lang/String;->length()I
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+ move-result v0
+ #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+ #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+ return v0
+ #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+ :cond_9
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+ const v0, -0x1
+ #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+ #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+ return v0
+ #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+.end method
+
+.method public testInstanceOfNez(Ljava/lang/Object;)I
+ .registers 3
+
+ #v0=(Uninit);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+ instance-of v0, p1, Ljava/lang/String;
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+ if-nez v0, :cond_8
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Unknown);
+
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+ const v0, -0x1
+ #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+ #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+ return v0
+ #v0=(Byte);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+
+ :cond_8
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+ invoke-virtual {p1}, Ljava/lang/String;->length()I
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+ #v0=(Boolean);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+ move-result v0
+ #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+
+ #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+ return v0
+ #v0=(Integer);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/String;);
+.end method
+
+.method public testRegisterAlias(Ljava/lang/Object;)I
+ .registers 4
+
+ #v0=(Uninit);v1=(Uninit);p0=(Reference,LInstanceOfTest;);p1=(Reference,Ljava/lang/Object;);
+ move-object p0, p1
+ #v0=(Uninit);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+
+ #v0=(Uninit);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+ instance-of v0, p0, Ljava/lang/String;
+ #v0=(Boolean);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+
+ #v0=(Boolean);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+ if-eqz v0, :cond_f
+ #v0=(Boolean);v1=(Uninit);p0=(Unknown);p1=(Unknown);
+
+ :cond_5
+ #v0=(Integer):merge{0x3:(Boolean),0xc:(Integer)}
+ #v1=(Conflicted):merge{0x3:(Uninit),0xc:(Null)}
+ #p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+ invoke-virtual {p1}, Ljava/lang/String;->length()I
+ #v0=(Integer);v1=(Conflicted);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+ #v0=(Integer);v1=(Conflicted);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+ move-result v0
+ #v0=(Integer);v1=(Conflicted);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+ #v0=(Integer);v1=(Conflicted);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+ const v1, 0x0
+ #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+ #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+ if-le v0, v1, :cond_5
+ #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+ #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+ return v0
+ #v0=(Integer);v1=(Null);p0=(Reference,Ljava/lang/String;);p1=(Reference,Ljava/lang/String;);
+
+ :cond_f
+ #v0=(Boolean);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+ const v0, -0x1
+ #v0=(Byte);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+
+ #v0=(Byte);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+ return v0
+ #v0=(Byte);v1=(Uninit);p0=(Reference,Ljava/lang/Object;);p1=(Reference,Ljava/lang/Object;);
+.end method
diff --git a/baksmali/src/test/resources/InstanceOfTest/classes.dex b/baksmali/src/test/resources/InstanceOfTest/classes.dex
new file mode 100644
index 00000000..571bdb8a
--- /dev/null
+++ b/baksmali/src/test/resources/InstanceOfTest/classes.dex
Binary files differ
diff --git a/baksmali/src/test/resources/UninitRefIdentityTest/UninitRefIdentityTest.smali b/baksmali/src/test/resources/UninitRefIdentityTest/UninitRefIdentityTest.smali
index f9c43637..1970d3b4 100644
--- a/baksmali/src/test/resources/UninitRefIdentityTest/UninitRefIdentityTest.smali
+++ b/baksmali/src/test/resources/UninitRefIdentityTest/UninitRefIdentityTest.smali
@@ -26,11 +26,37 @@
#v0=(Conflicted):merge{0x5:(UninitRef,Ljava/lang/String;),0x7:(UninitRef,Ljava/lang/String;)}
#v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
invoke-direct {v0}, Ljava/lang/String;-><init>()V
- #v0=(Unknown);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+ #v0=(Conflicted);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
- #v0=(Unknown);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+ #v0=(Conflicted);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
return-void
- #v0=(Unknown);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+ #v0=(Conflicted);v1=(Uninit);v2=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+.end method
+
+.method public constructor <init>(I)V
+ .registers 2
+
+ #p0=(UninitThis,LUninitRefIdentityTest;);p1=(Integer);
+ move-object p1, p0
+ #p0=(UninitThis,LUninitRefIdentityTest;);p1=(UninitThis,LUninitRefIdentityTest;);
+
+ #p0=(UninitThis,LUninitRefIdentityTest;);p1=(UninitThis,LUninitRefIdentityTest;);
+ invoke-direct {p1}, Ljava/lang/Object;-><init>()V
+ #p0=(Reference,LUninitRefIdentityTest;);p1=(Reference,LUninitRefIdentityTest;);
+
+ :cond_4
+ #p0=(Reference,LUninitRefIdentityTest;);
+ #p1=(Reference,LUninitRefIdentityTest;):merge{0x1:(Reference,LUninitRefIdentityTest;),0x7:(Null)}
+ const p1, 0x0
+ #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
+
+ #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
+ if-nez p1, :cond_4
+ #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
+
+ #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
+ return-void
+ #p0=(Reference,LUninitRefIdentityTest;);p1=(Null);
.end method
.method public constructor <init>(Ljava/lang/String;)V
@@ -48,3 +74,37 @@
return-void
#p0=(Reference,LUninitRefIdentityTest;);p1=(Reference,LUninitRefIdentityTest;);
.end method
+
+
+# virtual methods
+.method public overlappingInits()V
+ .registers 3
+
+ #v0=(Uninit);v1=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+ new-instance v0, Ljava/lang/String;
+ #v0=(UninitRef,Ljava/lang/String;);v1=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+
+ #v0=(UninitRef,Ljava/lang/String;);v1=(Uninit);p0=(Reference,LUninitRefIdentityTest;);
+ new-instance v1, Ljava/lang/String;
+ #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(Reference,LUninitRefIdentityTest;);
+
+ #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(Reference,LUninitRefIdentityTest;);
+ new-instance p0, Ljava/lang/String;
+ #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(UninitRef,Ljava/lang/String;);
+
+ #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(UninitRef,Ljava/lang/String;);
+ invoke-direct {p0}, Ljava/lang/String;-><init>()V
+ #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+
+ #v0=(UninitRef,Ljava/lang/String;);v1=(UninitRef,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+ invoke-direct {v1}, Ljava/lang/String;-><init>()V
+ #v0=(UninitRef,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+
+ #v0=(UninitRef,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+ invoke-direct {v0}, Ljava/lang/String;-><init>()V
+ #v0=(Reference,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+
+ #v0=(Reference,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+ return-void
+ #v0=(Reference,Ljava/lang/String;);v1=(Reference,Ljava/lang/String;);p0=(Reference,Ljava/lang/String;);
+.end method
diff --git a/baksmali/src/test/resources/UninitRefIdentityTest/classes.dex b/baksmali/src/test/resources/UninitRefIdentityTest/classes.dex
index ea146cf6..0f0caab3 100644
--- a/baksmali/src/test/resources/UninitRefIdentityTest/classes.dex
+++ b/baksmali/src/test/resources/UninitRefIdentityTest/classes.dex
Binary files differ
diff --git a/build.gradle b/build.gradle
index 56aaa3f6..9e8205d4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -31,9 +31,14 @@
apply plugin: 'idea'
-version = '2.1.3'
+version = '2.2.0'
+def jcommanderVersion = ''
if (!('release' in gradle.startParameter.taskNames)) {
+ // we compile against 1.48 normally, to match what's in AOSP, but switch to a newer version
+ // for release, because it has some fixes required when running on Android
+ jcommanderVersion = 'com.beust:jcommander:1.48'
+
def versionSuffix
try {
def git = org.eclipse.jgit.api.Git.open(file('.'))
@@ -51,6 +56,8 @@ if (!('release' in gradle.startParameter.taskNames)) {
version += "-${versionSuffix}"
} else {
+ jcommanderVersion = 'com.beust:jcommander:1.64'
+
if (System.env.JDK6_HOME == null && !JavaVersion.current().isJava6()) {
throw new InvalidUserDataException("bzzzzzzzt. Release builds must be performed with java 6. " +
"Either run gradle with java 6, or define the JDK6_HOME environment variable.")
@@ -101,15 +108,16 @@ subprojects {
guava: 'com.google.guava:guava:18.0',
findbugs: 'com.google.code.findbugs:jsr305:1.3.9',
junit: 'junit:junit:4.6',
+ mockito: 'org.mockito:mockito-core:1.10.19',
antlr_runtime: 'org.antlr:antlr-runtime:3.5.2',
antlr: 'org.antlr:antlr:3.5.2',
stringtemplate: 'org.antlr:stringtemplate:3.2.1',
- commons_cli: 'commons-cli:commons-cli:1.2',
jflex_plugin: 'org.xbib.gradle.plugin:gradle-plugin-jflex:1.1.0',
proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1',
dx: 'com.google.android.tools:dx:1.7',
- gson: 'com.google.code.gson:gson:2.3.1'
- ]
+ gson: 'com.google.code.gson:gson:2.3.1',
+ jcommander: jcommanderVersion
+ ]
}
repositories {
@@ -196,5 +204,6 @@ buildscript {
}
task wrapper(type: Wrapper) {
- gradleVersion = '2.14'
+ gradleVersion = '3.1'
+ distributionType = Wrapper.DistributionType.ALL
} \ No newline at end of file
diff --git a/dexlib2/OatVersions.txt b/dexlib2/OatVersions.txt
index 8aa9ea96..329c4f04 100644
--- a/dexlib2/OatVersions.txt
+++ b/dexlib2/OatVersions.txt
@@ -8,6 +8,7 @@ d7cbf8a6629942e7bd315ffae7e1c77b082f3e11 - 60
- return-void-barrier -> return-void-no-barrier
1412dfa4adcd511902e510fa0c948b168ab5840c - 61 (re-commit of f3251d12)
9d6bf69ad3012a9d843268fdd5325b6719b6d5f2 - 62
+- classpath list was added
0de1133ba600f299b3d67938f650720d9f859eb2 - 63
07785bb98dc8bbe192970e0f4c2cafd338a8dc68 - 64
fa2c054b28d4b540c1b3651401a7a091282a015f - 65
@@ -21,4 +22,69 @@ fab6788358dfb64e5c370611ddbbbffab0ed0553 - 67
6e2d5747d00697a25251d25dd33b953e54709507 - 68 (revert of 54b62480)
0747466fca310eedea5fc49e37d54f240a0b3c0f - 69 (re-commit of 54b62480)
501fd635a557645ab05f893c56e1f358e21bab82 - 70
-99170c636dfae4908b102347cfe9f92bad1881cc - 71 \ No newline at end of file
+99170c636dfae4908b102347cfe9f92bad1881cc - 71
+3cfa4d05afa76e19ca99ec964b535a15c73683f0 - 72
+- default methods
+d9786b0e5be23ea0258405165098b4216579209c - 73
+- fast class lookup table
+a4f1220c1518074db18ca1044e9201492975750b - 74
+625a64aad13905d8a2454bf3cc0e874487b110d5 - 75
+- bootclasspath list was added
+- class offsets moved out to a separate table
+919f5536182890d2e03f59b961acf8f7c836ff61 - 74 (revert of 625a64aa)
+9bdf108885a27ba05fae8501725649574d7c491b - 75 (re-commit of 625a64aa)
+a62d2f04a6ecf804f8a78e722a6ca8ccb2dfa931 - 76
+845e5064580bd37ad5014f7aa0d078be7265464d - 75 (revert of a62d2f04)
+29d38e77c553c6cf71fc4dafe2d22b4e3f814872 - 76 (re-commit of 845e5064)
+d1537b569b6cd18297c5e02d13cdd588c4366c51 - 77
+61b28a17d9b6e8e998103646e98e4a9772e11927 - 78
+9d07e3d128ccfa0ef7670feadd424a825e447d1d - 79
+952e1e3710158982941fc70326e9fddc3021235d - 80
+013e3f33495dcc31dba19c9de128d23ed441d7d3 - 81
+87f3fcbd0db352157fc59148e94647ef21b73bce - 82
+02b75806a80f8b75c3d6ba2ff97c995117630f36 - 83
+4359e61927866c254bc2d701e3ea4c48de10b79c - 84
+d549c28cfbddba945cb88857bcca3dce1414fb29 - 85
+952dbb19cd094b8bfb01dbb33e0878db429e499a - 86
+239d6eaff0cbb5c4c0139f7053a012758799f186 - 87 - introduction of vdex files
+77d9dd75d5d4a22ad1235f9a08d2cfbf2f0ae6fa - 89
+af1e2990cd1406a0fb7cba1d2e208208e950e413 - 90
+9fd8c60cdff7b28a89bb97fd90ae9d0f37cf8f8b - 91
+6beced4c017826f7c449f12fac7fa42403657f2b - 92
+58c3f6a0d15a4340c0a11ab7fbc8c4b990c64b77 - 93
+5923b5238091d9cd65f988fc059deb4fbb2e7f08 - 94
+2b615ba29c4dfcf54aaf44955f2eac60f5080b2e - 95
+f7aaacd97881c6924b8212c7f8fe4a4c8721ef53 - 94 (revert of 2b615ba)
+0d3998b5ff619364acf47bec0b541e7a49bd6fe7 - 95 (re-commit of 2b615ba)
+ac141397dc29189ad2b2df41f8d4312246beec60 - 96
+1998cd02603197f2acdc0734397a6d48b2f59b80 - 97
+e71b35446985835363a4508646cf7b1121bd95a3 - 98
+39cee66a8ddf0254626c9591662cf87e4a1cedc4 - 99
+cc99df230feb46ba717252f002d0cc2da6828421 - 100
+fee255039e30c1c3dfc70c426c3d176221c3cdf9 - 99 (revert of cc99df23)
+e761bccf9f0d884cc4d4ec104568cef968296492 - 100 (re-commit of cc99df23)
+8d91ac31ccb92557e434d89ffade3372466e1af5 - 101
+fd3161acfbe82c54ef49958f0ccc62511f224f91 - 102
+a2f526f889be06f96ea59624c9dfb1223b3839f3 - 103
+b048cb74b742b03eb6dd5f1d6dd49e559f730b36 - 104
+12f1b99775bbf7dd82d0a897587ab6ed0e75ee22 - 105
+ec7862283dd49f5a58d0ac45960ce27c2f7671b8 - 106
+45aa598cd1773f5eb1705dec13bea059238e054d - 107
+d16363a93053de0f32252c7897d839a46aff14ae - 108
+1a20b6801f2432a42b906f0de01e7e9586526aec - 109
+575d3e60c68b5cf481b615dde4a16283507b19ed - 110
+85c0f2ac03417f5125bc2ff1dab8109859c67d5c - 111
+5812e20ff7cbc8efa0b8d7486ada2f58840a6ad5 - 111
+b7ea3799c15b0090bb690e18ac1b5b0fddbdeee8 - 112
+ - version bump for missing bump in commits
+ - 3228908337fdfe851223f8ae374538de25cb5ad1
+ - 5812e20ff7cbc8efa0b8d7486ada2f58840a6ad5
+d776ff08e07494327716f0d2ea1a774b2ebfbca9 - 113
+bfb80d25eaeb7a604d5dd25a370e3869e96a33ab - 114
+1aea3510b8dd0c512cec61c91c5ef1f1e5d53d64 - 115
+6374c58f2ea403b3a05fb27376110fe4d0fc8e3f - 114 (revert of 1aea3510)
+0b66d6174bf1f6023f9d36dda8538490b79c2e9f - 113 (revert of bfb80d25)
+8d6768d47b66a688d35399d524ad5a5450e9d9d4 - 114 (i don't even)
+f44d36c8423f81cbb5e9f55d8813e26ffa1a7f3b - 115 (115 again. heck if I know what's going on)
+cbcedbf9382bc773713cd3552ed96f417bf1daeb - 116
+051071718085ce807a2e7c55278a8d723e238e86 - 116
diff --git a/dexlib2/VdexVersions.txt b/dexlib2/VdexVersions.txt
new file mode 100644
index 00000000..9cb1a731
--- /dev/null
+++ b/dexlib2/VdexVersions.txt
@@ -0,0 +1,8 @@
+7b49e6cade09bc65b3b5f22d45fc9d0a7184e4f2 - 0 - introduction of vdex files
+5d5a36bddbc008cd52a3207aa2b31177c47f9a49 - 0 - verifier deps
+4acefd33064d37b41ca55c3c9355345a20e5f9c2 - 0 - quickening info
+f54e5df37cb42d9a83fc54b375da5ef335d604a9 - 1 - dex file count + dex location checksum
+7498105ec7497bae2ba9f1a697da9efa0c979654 - 2 - verify profile
+3eba863e41d531340392d9ec64e17963ac898d81 - 3
+97fa9928c07d3e0ee631235e9619fb0f8949ed7a - 4
+6e54f78c7c1e01c1a91a458c6e51cca1c7d13ad4 - 5
diff --git a/dexlib2/build.gradle b/dexlib2/build.gradle
index 8fbe5ffe..422d2c31 100644
--- a/dexlib2/build.gradle
+++ b/dexlib2/build.gradle
@@ -51,6 +51,7 @@ dependencies {
compile depends.guava
testCompile depends.junit
+ testCompile depends.mockito
accessorTestGenerator project('accessorTestGenerator')
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
index 60488ba2..1caaf9f8 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
@@ -31,14 +31,22 @@
package org.jf.dexlib2;
-import com.google.common.base.MoreObjects;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.OatFile.NotAnOatFileException;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
+import org.jf.dexlib2.dexbacked.OatFile.VdexProvider;
+import org.jf.dexlib2.dexbacked.ZipDexContainer;
+import org.jf.dexlib2.dexbacked.ZipDexContainer.NotAZipFileException;
import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.dexlib2.writer.pool.DexPool;
import org.jf.util.ExceptionWithContext;
@@ -46,80 +54,44 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
public final class DexFileFactory {
- @Nonnull
- public static DexBackedDexFile loadDexFile(@Nonnull String path, int api) throws IOException {
- return loadDexFile(path, api, false);
- }
-
- @Nonnull
- public static DexBackedDexFile loadDexFile(@Nonnull String path, int api, boolean experimental)
- throws IOException {
- return loadDexFile(new File(path), "classes.dex", Opcodes.forApi(api, experimental));
- }
@Nonnull
- public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api) throws IOException {
- return loadDexFile(dexFile, api, false);
+ public static DexBackedDexFile loadDexFile(@Nonnull String path, @Nonnull Opcodes opcodes) throws IOException {
+ return loadDexFile(new File(path), opcodes);
}
+ /**
+ * Loads a dex/apk/odex/oat file.
+ *
+ * For oat files with multiple dex files, the first will be opened. For zip/apk files, the "classes.dex" entry
+ * will be opened.
+ *
+ * @param file The file to open
+ * @param opcodes The set of opcodes to use
+ * @return A DexBackedDexFile for the given file
+ *
+ * @throws UnsupportedOatVersionException If file refers to an unsupported oat file
+ * @throws DexFileNotFoundException If file does not exist, if file is a zip file but does not have a "classes.dex"
+ * entry, or if file is an oat file that has no dex entries.
+ * @throws UnsupportedFileTypeException If file is not a valid dex/zip/odex/oat file, or if the "classes.dex" entry
+ * in a zip file is not a valid dex file
+ */
@Nonnull
- public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, int api, boolean experimental)
- throws IOException {
- return loadDexFile(dexFile, null, Opcodes.forApi(api, experimental));
- }
-
- @Nonnull
- public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry, int api,
- boolean experimental) throws IOException {
- return loadDexFile(dexFile, dexEntry, Opcodes.forApi(api, experimental));
- }
+ public static DexBackedDexFile loadDexFile(@Nonnull File file, @Nonnull Opcodes opcodes) throws IOException {
+ if (!file.exists()) {
+ throw new DexFileNotFoundException("%s does not exist", file.getName());
+ }
- @Nonnull
- public static DexBackedDexFile loadDexFile(@Nonnull File dexFile, @Nullable String dexEntry,
- @Nonnull Opcodes opcodes) throws IOException {
- ZipFile zipFile = null;
- boolean isZipFile = false;
try {
- zipFile = new ZipFile(dexFile);
- // if we get here, it's safe to assume we have a zip file
- isZipFile = true;
-
- String zipEntryName = MoreObjects.firstNonNull(dexEntry, "classes.dex");
- ZipEntry zipEntry = zipFile.getEntry(zipEntryName);
- if (zipEntry == null) {
- throw new DexFileNotFound("zip file %s does not contain a %s file", dexFile.getName(), zipEntryName);
- }
- long fileLength = zipEntry.getSize();
- if (fileLength < 40) {
- throw new ExceptionWithContext("The %s file in %s is too small to be a valid dex file",
- zipEntryName, dexFile.getName());
- } else if (fileLength > Integer.MAX_VALUE) {
- throw new ExceptionWithContext("The %s file in %s is too large to read in",
- zipEntryName, dexFile.getName());
- }
- byte[] dexBytes = new byte[(int)fileLength];
- ByteStreams.readFully(zipFile.getInputStream(zipEntry), dexBytes);
- return new DexBackedDexFile(opcodes, dexBytes);
- } catch (IOException ex) {
- // don't continue on if we know it's a zip file
- if (isZipFile) {
- throw ex;
- }
- } finally {
- if (zipFile != null) {
- try {
- zipFile.close();
- } catch (IOException ex) {
- // just eat it
- }
- }
+ ZipDexContainer container = new ZipDexContainer(file, opcodes);
+ return new DexEntryFinder(file.getPath(), container).findEntry("classes.dex", true);
+ } catch (NotAZipFileException ex) {
+ // eat it and continue
}
- InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));
+ InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
try {
try {
return DexBackedDexFile.fromInputStream(opcodes, inputStream);
@@ -127,17 +99,18 @@ public final class DexFileFactory {
// just eat it
}
- // Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails
-
try {
return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
} catch (DexBackedOdexFile.NotAnOdexFile ex) {
// just eat it
}
+ // Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
+ // back to the same position, if they fails
+
OatFile oatFile = null;
try {
- oatFile = OatFile.fromInputStream(inputStream);
+ oatFile = OatFile.fromInputStream(inputStream, new FilenameVdexProvider(file));
} catch (NotAnOatFileException ex) {
// just eat it
}
@@ -150,77 +123,373 @@ public final class DexFileFactory {
List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
if (oatDexFiles.size() == 0) {
- throw new DexFileNotFound("Oat file %s contains no dex files", dexFile.getName());
+ throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
}
- if (dexEntry == null) {
- if (oatDexFiles.size() > 1) {
- throw new MultipleDexFilesException(oatFile);
- }
- return oatDexFiles.get(0);
- } else {
- // first check for an exact match
- for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
- if (oatDexFile.filename.equals(dexEntry)) {
- return oatDexFile;
- }
- }
+ return oatDexFiles.get(0);
+ }
+ } finally {
+ inputStream.close();
+ }
- if (!dexEntry.contains("/")) {
- for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
- File oatEntryFile = new File(oatDexFile.filename);
- if (oatEntryFile.getName().equals(dexEntry)) {
- return oatDexFile;
- }
- }
- }
+ throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
+ }
+
+ /**
+ * Loads a dex entry from a container format (zip/oat)
+ *
+ * This has two modes of operation, depending on the exactMatch parameter. When exactMatch is true, it will only
+ * load an entry whose name exactly matches that provided by the dexEntry parameter.
+ *
+ * When exactMatch is false, then it will search for any entry that dexEntry is a path suffix of. "path suffix"
+ * meaning all the path components in dexEntry must fully match the corresponding path components in the entry name,
+ * but some path components at the beginning of entry name can be missing.
+ *
+ * For example, if an oat file contains a "/system/framework/framework.jar:classes2.dex" entry, then the following
+ * will match (not an exhaustive list):
+ *
+ * "/system/framework/framework.jar:classes2.dex"
+ * "system/framework/framework.jar:classes2.dex"
+ * "framework/framework.jar:classes2.dex"
+ * "framework.jar:classes2.dex"
+ * "classes2.dex"
+ *
+ * Note that partial path components specifically don't match. So something like "work/framework.jar:classes2.dex"
+ * would not match.
+ *
+ * If dexEntry contains an initial slash, it will be ignored for purposes of this suffix match -- but not when
+ * performing an exact match.
+ *
+ * If multiple entries match the given dexEntry, a MultipleMatchingDexEntriesException will be thrown
+ *
+ * @param file The container file. This must be either a zip (apk) file or an oat file.
+ * @param dexEntry The name of the entry to load. This can either be the exact entry name, if exactMatch is true,
+ * or it can be a path suffix.
+ * @param exactMatch If true, dexE
+ * @param opcodes The set of opcodes to use
+ * @return A DexBackedDexFile for the given entry
+ *
+ * @throws UnsupportedOatVersionException If file refers to an unsupported oat file
+ * @throws DexFileNotFoundException If the file does not exist, or if no matching entry could be found
+ * @throws UnsupportedFileTypeException If file is not a valid zip/oat file, or if the matching entry is not a
+ * valid dex file
+ * @throws MultipleMatchingDexEntriesException If multiple entries match the given dexEntry
+ */
+ public static DexBackedDexFile loadDexEntry(@Nonnull File file, @Nonnull String dexEntry,
+ boolean exactMatch, @Nonnull Opcodes opcodes) throws IOException {
+ if (!file.exists()) {
+ throw new DexFileNotFoundException("Container file %s does not exist", file.getName());
+ }
+
+ try {
+ ZipDexContainer container = new ZipDexContainer(file, opcodes);
+ return new DexEntryFinder(file.getPath(), container).findEntry(dexEntry, exactMatch);
+ } catch (NotAZipFileException ex) {
+ // eat it and continue
+ }
+
+ InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
+ try {
+ OatFile oatFile = null;
+ try {
+ oatFile = OatFile.fromInputStream(inputStream, new FilenameVdexProvider(file));
+ } catch (NotAnOatFileException ex) {
+ // just eat it
+ }
- throw new DexFileNotFound("oat file %s does not contain a dex file named %s",
- dexFile.getName(), dexEntry);
+ if (oatFile != null) {
+ if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
+ throw new UnsupportedOatVersionException(oatFile);
+ }
+
+ List<OatDexFile> oatDexFiles = oatFile.getDexFiles();
+
+ if (oatDexFiles.size() == 0) {
+ throw new DexFileNotFoundException("Oat file %s contains no dex files", file.getName());
}
+
+ return new DexEntryFinder(file.getPath(), oatFile).findEntry(dexEntry, exactMatch);
}
} finally {
inputStream.close();
}
- throw new ExceptionWithContext("%s is not an apk, dex, odex or oat file.", dexFile.getPath());
+ throw new UnsupportedFileTypeException("%s is not an apk or oat file.", file.getPath());
}
+ /**
+ * Loads a file containing 1 or more dex files
+ *
+ * If the given file is a dex or odex file, it will return a MultiDexContainer containing that single entry.
+ * Otherwise, for an oat or zip file, it will return an OatFile or ZipDexContainer respectively.
+ *
+ * @param file The file to open
+ * @param opcodes The set of opcodes to use
+ * @return A MultiDexContainer
+ * @throws DexFileNotFoundException If the given file does not exist
+ * @throws UnsupportedFileTypeException If the given file is not a valid dex/zip/odex/oat file
+ */
+ public static MultiDexContainer<? extends DexBackedDexFile> loadDexContainer(
+ @Nonnull File file, @Nonnull final Opcodes opcodes) throws IOException {
+ if (!file.exists()) {
+ throw new DexFileNotFoundException("%s does not exist", file.getName());
+ }
+
+ ZipDexContainer zipDexContainer = new ZipDexContainer(file, opcodes);
+ if (zipDexContainer.isZipFile()) {
+ return zipDexContainer;
+ }
+
+ InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
+ try {
+ try {
+ DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(opcodes, inputStream);
+ return new SingletonMultiDexContainer(file.getPath(), dexFile);
+ } catch (DexBackedDexFile.NotADexFile ex) {
+ // just eat it
+ }
+
+ try {
+ DexBackedOdexFile odexFile = DexBackedOdexFile.fromInputStream(opcodes, inputStream);
+ return new SingletonMultiDexContainer(file.getPath(), odexFile);
+ } catch (DexBackedOdexFile.NotAnOdexFile ex) {
+ // just eat it
+ }
+
+ // Note: DexBackedDexFile.fromInputStream and DexBackedOdexFile.fromInputStream will reset inputStream
+ // back to the same position, if they fails
+
+ OatFile oatFile = null;
+ try {
+ oatFile = OatFile.fromInputStream(inputStream, new FilenameVdexProvider(file));
+ } catch (NotAnOatFileException ex) {
+ // just eat it
+ }
+
+ if (oatFile != null) {
+ // TODO: we should support loading earlier oat files, just not deodexing them
+ if (oatFile.isSupportedVersion() == OatFile.UNSUPPORTED) {
+ throw new UnsupportedOatVersionException(oatFile);
+ }
+ return oatFile;
+ }
+ } finally {
+ inputStream.close();
+ }
+
+ throw new UnsupportedFileTypeException("%s is not an apk, dex, odex or oat file.", file.getPath());
+ }
+
+ /**
+ * Writes a DexFile out to disk
+ *
+ * @param path The path to write the dex file to
+ * @param dexFile a DexFile to write
+ */
public static void writeDexFile(@Nonnull String path, @Nonnull DexFile dexFile) throws IOException {
DexPool.writeTo(path, dexFile);
}
private DexFileFactory() {}
- public static class DexFileNotFound extends ExceptionWithContext {
- public DexFileNotFound(@Nullable Throwable cause) {
- super(cause);
+ public static class DexFileNotFoundException extends ExceptionWithContext {
+ public DexFileNotFoundException(@Nullable String message, Object... formatArgs) {
+ super(message, formatArgs);
}
+ }
+
+ public static class UnsupportedOatVersionException extends ExceptionWithContext {
+ @Nonnull public final OatFile oatFile;
- public DexFileNotFound(@Nullable Throwable cause, @Nullable String message, Object... formatArgs) {
- super(cause, message, formatArgs);
+ public UnsupportedOatVersionException(@Nonnull OatFile oatFile) {
+ super("Unsupported oat version: %d", oatFile.getOatVersion());
+ this.oatFile = oatFile;
}
+ }
- public DexFileNotFound(@Nullable String message, Object... formatArgs) {
- super(message, formatArgs);
+ public static class MultipleMatchingDexEntriesException extends ExceptionWithContext {
+ public MultipleMatchingDexEntriesException(@Nonnull String message, Object... formatArgs) {
+ super(String.format(message, formatArgs));
}
}
- public static class MultipleDexFilesException extends ExceptionWithContext {
- @Nonnull public final OatFile oatFile;
+ public static class UnsupportedFileTypeException extends ExceptionWithContext {
+ public UnsupportedFileTypeException(@Nonnull String message, Object... formatArgs) {
+ super(String.format(message, formatArgs));
+ }
+ }
- public MultipleDexFilesException(@Nonnull OatFile oatFile) {
- super("Oat file has multiple dex files.");
- this.oatFile = oatFile;
+ /**
+ * Matches two entries fully, ignoring any initial slash, if any
+ */
+ private static boolean fullEntryMatch(@Nonnull String entry, @Nonnull String targetEntry) {
+ if (entry.equals(targetEntry)) {
+ return true;
}
+
+ if (entry.charAt(0) == '/') {
+ entry = entry.substring(1);
+ }
+
+ if (targetEntry.charAt(0) == '/') {
+ targetEntry = targetEntry.substring(1);
+ }
+
+ return entry.equals(targetEntry);
}
- public static class UnsupportedOatVersionException extends ExceptionWithContext {
- @Nonnull public final OatFile oatFile;
+ /**
+ * Performs a partial match against entry and targetEntry.
+ *
+ * This is considered a partial match if targetEntry is a suffix of entry, and if the suffix starts
+ * on a path "part" (ignoring the initial separator, if any). Both '/' and ':' are considered separators for this.
+ *
+ * So entry="/blah/blah/something.dex" and targetEntry="lah/something.dex" shouldn't match, but
+ * both targetEntry="blah/something.dex" and "/blah/something.dex" should match.
+ */
+ private static boolean partialEntryMatch(String entry, String targetEntry) {
+ if (entry.equals(targetEntry)) {
+ return true;
+ }
- public UnsupportedOatVersionException(@Nonnull OatFile oatFile) {
- super("Unsupported oat version: %d", oatFile.getOatVersion());
- this.oatFile = oatFile;
+ if (!entry.endsWith(targetEntry)) {
+ return false;
+ }
+
+ // Make sure the first matching part is a full entry. We don't want to match "/blah/blah/something.dex" with
+ // "lah/something.dex", but both "/blah/something.dex" and "blah/something.dex" should match
+ char precedingChar = entry.charAt(entry.length() - targetEntry.length() - 1);
+ char firstTargetChar = targetEntry.charAt(0);
+ // This is a device path, so we should always use the linux separator '/', rather than the current platform's
+ // separator
+ return firstTargetChar == ':' || firstTargetChar == '/' || precedingChar == ':' || precedingChar == '/';
+ }
+
+ protected static class DexEntryFinder {
+ private final String filename;
+ private final MultiDexContainer<? extends DexBackedDexFile> dexContainer;
+
+ public DexEntryFinder(@Nonnull String filename,
+ @Nonnull MultiDexContainer<? extends DexBackedDexFile> dexContainer) {
+ this.filename = filename;
+ this.dexContainer = dexContainer;
+ }
+
+ @Nonnull
+ public DexBackedDexFile findEntry(@Nonnull String targetEntry, boolean exactMatch) throws IOException {
+ if (exactMatch) {
+ try {
+ DexBackedDexFile dexFile = dexContainer.getEntry(targetEntry);
+ if (dexFile == null) {
+ throw new DexFileNotFoundException("Could not find entry %s in %s.", targetEntry, filename);
+ }
+ return dexFile;
+ } catch (NotADexFile ex) {
+ throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file", targetEntry, filename);
+ }
+ }
+
+ // find all full and partial matches
+ List<String> fullMatches = Lists.newArrayList();
+ List<DexBackedDexFile> fullEntries = Lists.newArrayList();
+ List<String> partialMatches = Lists.newArrayList();
+ List<DexBackedDexFile> partialEntries = Lists.newArrayList();
+ for (String entry: dexContainer.getDexEntryNames()) {
+ if (fullEntryMatch(entry, targetEntry)) {
+ // We want to grab all full matches, regardless of whether they're actually a dex file.
+ fullMatches.add(entry);
+ fullEntries.add(dexContainer.getEntry(entry));
+ } else if (partialEntryMatch(entry, targetEntry)) {
+ partialMatches.add(entry);
+ partialEntries.add(dexContainer.getEntry(entry));
+ }
+ }
+
+ // full matches always take priority
+ if (fullEntries.size() == 1) {
+ try {
+ DexBackedDexFile dexFile = fullEntries.get(0);
+ assert dexFile != null;
+ return dexFile;
+ } catch (NotADexFile ex) {
+ throw new UnsupportedFileTypeException("Entry %s in %s is not a dex file",
+ fullMatches.get(0), filename);
+ }
+ }
+ if (fullEntries.size() > 1) {
+ // This should be quite rare. This would only happen if an oat file has two entries that differ
+ // only by an initial path separator. e.g. "/blah/blah.dex" and "blah/blah.dex"
+ throw new MultipleMatchingDexEntriesException(String.format(
+ "Multiple entries in %s match %s: %s", filename, targetEntry,
+ Joiner.on(", ").join(fullMatches)));
+ }
+
+ if (partialEntries.size() == 0) {
+ throw new DexFileNotFoundException("Could not find a dex entry in %s matching %s",
+ filename, targetEntry);
+ }
+ if (partialEntries.size() > 1) {
+ throw new MultipleMatchingDexEntriesException(String.format(
+ "Multiple dex entries in %s match %s: %s", filename, targetEntry,
+ Joiner.on(", ").join(partialMatches)));
+ }
+ return partialEntries.get(0);
+ }
+ }
+
+ private static class SingletonMultiDexContainer implements MultiDexContainer<DexBackedDexFile> {
+ private final String entryName;
+ private final DexBackedDexFile dexFile;
+
+ public SingletonMultiDexContainer(@Nonnull String entryName, @Nonnull DexBackedDexFile dexFile) {
+ this.entryName = entryName;
+ this.dexFile = dexFile;
+ }
+
+ @Nonnull @Override public List<String> getDexEntryNames() throws IOException {
+ return ImmutableList.of(entryName);
+ }
+
+ @Nullable @Override public DexBackedDexFile getEntry(@Nonnull String entryName) throws IOException {
+ if (entryName.equals(this.entryName)) {
+ return dexFile;
+ }
+ return null;
+ }
+
+ @Nonnull @Override public Opcodes getOpcodes() {
+ return dexFile.getOpcodes();
+ }
+ }
+
+ public static class FilenameVdexProvider implements VdexProvider {
+ private final File vdexFile;
+
+ @Nullable
+ private byte[] buf = null;
+ private boolean loadedVdex = false;
+
+ public FilenameVdexProvider(File oatFile) {
+ File oatParent = oatFile.getAbsoluteFile().getParentFile();
+ String baseName = Files.getNameWithoutExtension(oatFile.getAbsolutePath());
+ vdexFile = new File(oatParent, baseName + ".vdex");
+ }
+
+ @Nullable @Override public byte[] getVdex() {
+ if (!loadedVdex) {
+ if (vdexFile.exists()) {
+ try {
+ buf = ByteStreams.toByteArray(new FileInputStream(vdexFile));
+ } catch (FileNotFoundException e) {
+ buf = null;
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ loadedVdex = true;
+ }
+
+ return buf;
}
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java b/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java
index 843550f8..60dffa2f 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java
@@ -330,8 +330,6 @@ public enum Opcode
public static final int JUMBO_OPCODE = 0x200;
//if the instruction can initialize an uninitialized object reference
public static final int CAN_INITIALIZE_REFERENCE = 0x400;
- //if the instruction is experimental (not potentially supported by Android runtime yet)
- public static final int EXPERIMENTAL = 0x800;
private static final int ALL_APIS = 0xFFFF0000;
@@ -471,10 +469,6 @@ public enum Opcode
return (flags & CAN_INITIALIZE_REFERENCE) != 0;
}
- public final boolean isExperimental() {
- return (flags & EXPERIMENTAL) != 0;
- }
-
private static class VersionConstraint {
@Nonnull public final Range<Integer> apiRange;
@Nonnull public final Range<Integer> artVersionRange;
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java b/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
index a137dee2..5f8106d2 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
@@ -56,41 +56,32 @@ public class Opcodes {
@Nonnull
public static Opcodes forApi(int api) {
- return new Opcodes(api, NO_VERSION, false);
- }
-
- @Nonnull
- public static Opcodes forApi(int api, boolean experimental) {
- return new Opcodes(api, NO_VERSION, experimental);
+ return new Opcodes(api, NO_VERSION);
}
@Nonnull
public static Opcodes forArtVersion(int artVersion) {
- return forArtVersion(artVersion, false);
+ return new Opcodes(NO_VERSION, artVersion);
}
+ /**
+ * @return a default Opcodes instance for when the exact Opcodes to use doesn't matter or isn't known
+ */
@Nonnull
- public static Opcodes forArtVersion(int artVersion, boolean experimental) {
- return new Opcodes(NO_VERSION, artVersion, experimental);
+ public static Opcodes getDefault() {
+ // The last pre-art api
+ return forApi(20);
}
- @Deprecated
- public Opcodes(int api) {
- this(api, false);
- }
+ private Opcodes(int api, int artVersion) {
- @Deprecated
- public Opcodes(int api, boolean experimental) {
- this(api, VersionMap.mapApiToArtVersion(api), experimental);
- }
- private Opcodes(int api, int artVersion, boolean experimental) {
if (api >= 21) {
- this.api = api;
+ this.api = api;
this.artVersion = mapApiToArtVersion(api);
} else if (artVersion >= 0 && artVersion < 39) {
this.api = mapArtVersionToApi(artVersion);
- this.artVersion = artVersion;
+ this.artVersion = artVersion;
} else {
this.api = api;
this.artVersion = artVersion;
@@ -116,7 +107,7 @@ public class Opcodes {
}
Short opcodeValue = versionToValueMap.get(version);
- if (opcodeValue != null && (!opcode.isExperimental() || experimental)) {
+ if (opcodeValue != null) {
if (!opcode.format.isPayloadFormat) {
opcodesByValue[opcodeValue] = opcode;
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java
index 55f1ddc7..1a9b9ad1 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java
@@ -32,12 +32,14 @@
package org.jf.dexlib2.analysis;
import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.iface.instruction.*;
import org.jf.dexlib2.iface.instruction.formats.Instruction22c;
import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.iface.reference.Reference;
+import org.jf.dexlib2.iface.reference.TypeReference;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
@@ -54,7 +56,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
/**
* The actual instruction
*/
- @Nullable
+ @Nonnull
protected Instruction instruction;
/**
@@ -65,21 +67,25 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
/**
* Instructions that can pass on execution to this one during normal execution
*/
+ @Nonnull
protected final TreeSet<AnalyzedInstruction> predecessors = new TreeSet<AnalyzedInstruction>();
/**
* Instructions that can execution could pass on to next during normal execution
*/
+ @Nonnull
protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
/**
* This contains the register types *before* the instruction has executed
*/
+ @Nonnull
protected final RegisterType[] preRegisterMap;
/**
* This contains the register types *after* the instruction has executed
*/
+ @Nonnull
protected final RegisterType[] postRegisterMap;
/**
@@ -94,8 +100,8 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
*/
protected final Instruction originalInstruction;
- public AnalyzedInstruction(MethodAnalyzer methodAnalyzer, Instruction instruction, int instructionIndex,
- int registerCount) {
+ public AnalyzedInstruction(@Nonnull MethodAnalyzer methodAnalyzer, @Nonnull Instruction instruction,
+ int instructionIndex, int registerCount) {
this.methodAnalyzer = methodAnalyzer;
this.instruction = instruction;
this.originalInstruction = instruction;
@@ -150,18 +156,17 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
instruction = originalInstruction;
}
- public int getSuccessorCount() {
- return successors.size();
- }
-
- public List<AnalyzedInstruction> getSuccesors() {
+ @Nonnull
+ public List<AnalyzedInstruction> getSuccessors() {
return Collections.unmodifiableList(successors);
}
+ @Nonnull
public Instruction getInstruction() {
return instruction;
}
+ @Nonnull
public Instruction getOriginalInstruction() {
return originalInstruction;
}
@@ -184,11 +189,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
if (predecessors.size() == 0) {
return false;
}
-
- if (predecessors.first().instructionIndex == -1) {
- return true;
- }
- return false;
+ return predecessors.first().instructionIndex == -1;
}
/*
@@ -237,6 +238,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
* @param registerNumber the register number
* @return The register type resulting from merging the post-instruction register types from all predecessors
*/
+ @Nonnull
protected RegisterType getMergedPreRegisterTypeFromPredecessors(int registerNumber) {
RegisterType mergedRegisterType = null;
for (AnalyzedInstruction predecessor: predecessors) {
@@ -249,6 +251,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
}
}
}
+ if (mergedRegisterType == null) {
+ // This is a start-of-method or unreachable instruction.
+ throw new IllegalStateException();
+ }
return mergedRegisterType;
}
/**
@@ -275,10 +281,10 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
*
* This is used to set the register type for only one branch from a conditional jump.
*
- * @param predecessor Which predecessor is being overriden
- * @param registerNumber The register number of the register being overriden
+ * @param predecessor Which predecessor is being overridden
+ * @param registerNumber The register number of the register being overridden
* @param registerType The overridden register type
- * @param verifiedInstructions
+ * @param verifiedInstructions A bit vector of instructions that have been verified
*
* @return true if the post-instruction register type for this instruction changed as a result of this override
*/
@@ -308,8 +314,8 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
return false;
}
- protected boolean isInvokeInit() {
- if (instruction == null || !instruction.getOpcode().canInitializeReference()) {
+ public boolean isInvokeInit() {
+ if (!instruction.getOpcode().canInitializeReference()) {
return false;
}
@@ -323,23 +329,26 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
return false;
}
- public boolean setsRegister() {
- return instruction.getOpcode().setsRegister();
- }
-
- public boolean setsWideRegister() {
- return instruction.getOpcode().setsWideRegister();
- }
-
+ /**
+ * Determines if this instruction sets the given register, or alters its type
+ *
+ * @param registerNumber The register to check
+ * @return true if this instruction sets the given register or alters its type
+ */
public boolean setsRegister(int registerNumber) {
- //When constructing a new object, the register type will be an uninitialized reference after the new-instance
- //instruction, but becomes an initialized reference once the <init> method is called. So even though invoke
- //instructions don't normally change any registers, calling an <init> method will change the type of its
- //object register. If the uninitialized reference has been copied to other registers, they will be initialized
- //as well, so we need to check for that too
+ // This method could be implemented by calling getSetRegisters and checking if registerNumber is in the result
+ // However, this is a frequently called method, and this is a more efficient implementation, because it doesn't
+ // allocate a new list, and it can potentially exit earlier
+
if (isInvokeInit()) {
+ // When constructing a new object, the register type will be an uninitialized reference after the
+ // new-instance instruction, but becomes an initialized reference once the <init> method is called. So even
+ // though invoke instructions don't normally change any registers, calling an <init> method will change the
+ // type of its object register. If the uninitialized reference has been copied to other registers, they will
+ // be initialized as well, so we need to check for that too
int destinationRegister;
if (instruction instanceof FiveRegisterInstruction) {
+ assert ((FiveRegisterInstruction)instruction).getRegisterCount() > 0;
destinationRegister = ((FiveRegisterInstruction)instruction).getRegisterC();
} else {
assert instruction instanceof RegisterRangeInstruction;
@@ -348,34 +357,107 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
destinationRegister = rangeInstruction.getStartRegister();
}
- if (registerNumber == destinationRegister) {
- return true;
+ RegisterType preInstructionDestRegisterType = getPreInstructionRegisterType(destinationRegister);
+ if (preInstructionDestRegisterType.category == RegisterType.UNKNOWN) {
+ // We never let an uninitialized reference propagate past an invoke-init if the object register type is
+ // unknown This is because the uninitialized reference may be an alias to the reference being
+ // initialized, but we can't know that until the object register's type is known
+ RegisterType preInstructionRegisterType = getPreInstructionRegisterType(registerNumber);
+ if (preInstructionRegisterType.category == RegisterType.UNINIT_REF ||
+ preInstructionRegisterType.category == RegisterType.UNINIT_THIS) {
+ return true;
+ }
}
- RegisterType preInstructionDestRegisterType = getPreInstructionRegisterType(registerNumber);
- if (preInstructionDestRegisterType.category != RegisterType.UNINIT_REF &&
- preInstructionDestRegisterType.category != RegisterType.UNINIT_THIS) {
+ if (preInstructionDestRegisterType.category != RegisterType.UNINIT_REF &&
+ preInstructionDestRegisterType.category != RegisterType.UNINIT_THIS) {
return false;
}
- //check if the uninit ref has been copied to another register
- if (getPreInstructionRegisterType(registerNumber).equals(preInstructionDestRegisterType)) {
+
+ if (registerNumber == destinationRegister) {
return true;
}
- return false;
+
+ //check if the uninit ref has been copied to another register
+ return preInstructionDestRegisterType.equals(getPreInstructionRegisterType(registerNumber));
}
- if (instruction.getOpcode() == Opcode.IF_EQZ || instruction.getOpcode() == Opcode.IF_NEZ) {
- AnalyzedInstruction previousInstruction = getPreviousInstruction();
- if (previousInstruction != null &&
- previousInstruction.instruction != null &&
- previousInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF &&
- registerNumber == ((Instruction22c)previousInstruction.instruction).getRegisterB() &&
- MethodAnalyzer.canNarrowAfterInstanceOf(previousInstruction, this, methodAnalyzer.getClassPath())) {
- return true;
+ // On art, the optimizer will often nop out a check-cast instruction after an instance-of instruction.
+ // Normally, check-cast is where the register type actually changes.
+ // In order to correctly handle this case, we have to propagate the narrowed register type to the appropriate
+ // branch of the following if-eqz/if-nez
+ if (instructionIndex > 0 &&
+ methodAnalyzer.getClassPath().isArt() &&
+ getPredecessorCount() == 1 &&
+ (instruction.getOpcode() == Opcode.IF_EQZ || instruction.getOpcode() == Opcode.IF_NEZ)) {
+
+ AnalyzedInstruction prevInstruction = predecessors.first();
+ if (prevInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF &&
+ MethodAnalyzer.canPropagateTypeAfterInstanceOf(
+ prevInstruction, this, methodAnalyzer.getClassPath())) {
+ Instruction22c instanceOfInstruction = (Instruction22c)prevInstruction.instruction;
+
+ if (registerNumber == instanceOfInstruction.getRegisterB()) {
+ return true;
+ }
+
+ // Additionally, there may be a move instruction just before the instance-of, in order to put the value
+ // into a register that is addressable by the instance-of. In this case, we also need to propagate the
+ // new register type for the original register that the value was moved from.
+ // In some cases, the instance-of may have multiple predecessors. In this case, we should only do the
+ // propagation if all predecessors are move-object instructions for the same source register
+ // TODO: do we need to do some sort of additional check that these multiple move-object predecessors actually refer to the same value?
+ if (instructionIndex > 1) {
+ int originalSourceRegister = -1;
+
+ RegisterType newType = null;
+
+ for (AnalyzedInstruction prevPrevAnalyzedInstruction : prevInstruction.predecessors) {
+ Opcode opcode = prevPrevAnalyzedInstruction.instruction.getOpcode();
+ if (opcode == Opcode.MOVE_OBJECT || opcode == Opcode.MOVE_OBJECT_16 ||
+ opcode == Opcode.MOVE_OBJECT_FROM16) {
+ TwoRegisterInstruction moveInstruction =
+ ((TwoRegisterInstruction)prevPrevAnalyzedInstruction.instruction);
+ RegisterType originalType =
+ prevPrevAnalyzedInstruction.getPostInstructionRegisterType(
+ moveInstruction.getRegisterB());
+ if (moveInstruction.getRegisterA() != instanceOfInstruction.getRegisterB()) {
+ originalSourceRegister = -1;
+ break;
+ }
+ if (originalType.type == null) {
+ originalSourceRegister = -1;
+ break;
+ }
+
+ if (newType == null) {
+ newType = RegisterType.getRegisterType(methodAnalyzer.getClassPath(),
+ (TypeReference)instanceOfInstruction.getReference());
+ }
+
+ if (MethodAnalyzer.isNotWideningConversion(originalType, newType)) {
+ if (originalSourceRegister != -1) {
+ if (originalSourceRegister != moveInstruction.getRegisterB()) {
+ originalSourceRegister = -1;
+ break;
+ }
+ } else {
+ originalSourceRegister = moveInstruction.getRegisterB();
+ }
+ }
+ } else {
+ originalSourceRegister = -1;
+ break;
+ }
+ }
+ if (originalSourceRegister != -1 && registerNumber == originalSourceRegister) {
+ return true;
+ }
+ }
}
}
- if (!setsRegister()) {
+ if (!instruction.getOpcode().setsRegister()) {
return false;
}
int destinationRegister = getDestinationRegister();
@@ -383,20 +465,151 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
if (registerNumber == destinationRegister) {
return true;
}
- if (setsWideRegister() && registerNumber == (destinationRegister + 1)) {
+ if (instruction.getOpcode().setsWideRegister() && registerNumber == (destinationRegister + 1)) {
return true;
}
return false;
}
- @Nullable
- private AnalyzedInstruction getPreviousInstruction() {
- for (AnalyzedInstruction predecessor: predecessors) {
- if (predecessor.getInstructionIndex() == getInstructionIndex() - 1) {
- return predecessor;
+ public List<Integer> getSetRegisters() {
+ List<Integer> setRegisters = Lists.newArrayList();
+
+ if (instruction.getOpcode().setsRegister()) {
+ setRegisters.add(getDestinationRegister());
+ }
+ if (instruction.getOpcode().setsWideRegister()) {
+ setRegisters.add(getDestinationRegister() + 1);
+ }
+
+ if (isInvokeInit()) {
+ //When constructing a new object, the register type will be an uninitialized reference after the new-instance
+ //instruction, but becomes an initialized reference once the <init> method is called. So even though invoke
+ //instructions don't normally change any registers, calling an <init> method will change the type of its
+ //object register. If the uninitialized reference has been copied to other registers, they will be initialized
+ //as well, so we need to check for that too
+
+ int destinationRegister;
+ if (instruction instanceof FiveRegisterInstruction) {
+ destinationRegister = ((FiveRegisterInstruction)instruction).getRegisterC();
+ assert ((FiveRegisterInstruction)instruction).getRegisterCount() > 0;
+ } else {
+ assert instruction instanceof RegisterRangeInstruction;
+ RegisterRangeInstruction rangeInstruction = (RegisterRangeInstruction)instruction;
+ assert rangeInstruction.getRegisterCount() > 0;
+ destinationRegister = rangeInstruction.getStartRegister();
+ }
+
+ RegisterType preInstructionDestRegisterType = getPreInstructionRegisterType(destinationRegister);
+ if (preInstructionDestRegisterType.category == RegisterType.UNINIT_REF ||
+ preInstructionDestRegisterType.category == RegisterType.UNINIT_THIS) {
+ setRegisters.add(destinationRegister);
+
+ RegisterType objectRegisterType = preRegisterMap[destinationRegister];
+ for (int i = 0; i < preRegisterMap.length; i++) {
+ if (i == destinationRegister) {
+ continue;
+ }
+
+ RegisterType preInstructionRegisterType = preRegisterMap[i];
+
+ if (preInstructionRegisterType.equals(objectRegisterType)) {
+ setRegisters.add(i);
+ } else if (preInstructionRegisterType.category == RegisterType.UNINIT_REF ||
+ preInstructionRegisterType.category == RegisterType.UNINIT_THIS) {
+ RegisterType postInstructionRegisterType = postRegisterMap[i];
+ if (postInstructionRegisterType.category == RegisterType.UNKNOWN) {
+ setRegisters.add(i);
+ }
+ }
+ }
+ } else if (preInstructionDestRegisterType.category == RegisterType.UNKNOWN) {
+ // We never let an uninitialized reference propagate past an invoke-init if the object register type is
+ // unknown This is because the uninitialized reference may be an alias to the reference being
+ // initialized, but we can't know that until the object register's type is known
+
+ for (int i = 0; i < preRegisterMap.length; i++) {
+ RegisterType registerType = preRegisterMap[i];
+ if (registerType.category == RegisterType.UNINIT_REF ||
+ registerType.category == RegisterType.UNINIT_THIS) {
+ setRegisters.add(i);
+ }
+ }
+ }
+ }
+
+ // On art, the optimizer will often nop out a check-cast instruction after an instance-of instruction.
+ // Normally, check-cast is where the register type actually changes.
+ // In order to correctly handle this case, we have to propagate the narrowed register type to the appropriate
+ // branch of the following if-eqz/if-nez
+ if (instructionIndex > 0 &&
+ methodAnalyzer.getClassPath().isArt() &&
+ getPredecessorCount() == 1 &&
+ (instruction.getOpcode() == Opcode.IF_EQZ || instruction.getOpcode() == Opcode.IF_NEZ)) {
+
+ AnalyzedInstruction prevInstruction = predecessors.first();
+ if (prevInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF &&
+ MethodAnalyzer.canPropagateTypeAfterInstanceOf(
+ prevInstruction, this, methodAnalyzer.getClassPath())) {
+ Instruction22c instanceOfInstruction = (Instruction22c)prevInstruction.instruction;
+ setRegisters.add(instanceOfInstruction.getRegisterB());
+
+ // Additionally, there may be a move instruction just before the instance-of, in order to put the value
+ // into a register that is addressable by the instance-of. In this case, we also need to propagate the
+ // new register type for the original register that the value was moved from.
+ // In some cases, the instance-of may have multiple predecessors. In this case, we should only do the
+ // propagation if all predecessors are move-object instructions for the same source register
+ // TODO: do we need to do some sort of additional check that these multiple move-object predecessors actually refer to the same value?
+ if (instructionIndex > 1) {
+ int originalSourceRegister = -1;
+
+ RegisterType newType = null;
+
+ for (AnalyzedInstruction prevPrevAnalyzedInstruction : prevInstruction.predecessors) {
+ Opcode opcode = prevPrevAnalyzedInstruction.instruction.getOpcode();
+ if (opcode == Opcode.MOVE_OBJECT || opcode == Opcode.MOVE_OBJECT_16 ||
+ opcode == Opcode.MOVE_OBJECT_FROM16) {
+ TwoRegisterInstruction moveInstruction =
+ ((TwoRegisterInstruction)prevPrevAnalyzedInstruction.instruction);
+ RegisterType originalType =
+ prevPrevAnalyzedInstruction.getPostInstructionRegisterType(
+ moveInstruction.getRegisterB());
+ if (moveInstruction.getRegisterA() != instanceOfInstruction.getRegisterB()) {
+ originalSourceRegister = -1;
+ break;
+ }
+ if (originalType.type == null) {
+ originalSourceRegister = -1;
+ break;
+ }
+
+ if (newType == null) {
+ newType = RegisterType.getRegisterType(methodAnalyzer.getClassPath(),
+ (TypeReference)instanceOfInstruction.getReference());
+ }
+
+ if (MethodAnalyzer.isNotWideningConversion(originalType, newType)) {
+ if (originalSourceRegister != -1) {
+ if (originalSourceRegister != moveInstruction.getRegisterB()) {
+ originalSourceRegister = -1;
+ break;
+ }
+ } else {
+ originalSourceRegister = moveInstruction.getRegisterB();
+ }
+ }
+ } else {
+ originalSourceRegister = -1;
+ break;
+ }
+ }
+ if (originalSourceRegister != -1) {
+ setRegisters.add(originalSourceRegister);
+ }
+ }
}
}
- return null;
+
+ return setRegisters;
}
public int getDestinationRegister() {
@@ -421,7 +634,7 @@ public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
return preRegisterMap[registerNumber];
}
- public int compareTo(AnalyzedInstruction analyzedInstruction) {
+ public int compareTo(@Nonnull AnalyzedInstruction analyzedInstruction) {
if (instructionIndex < analyzedInstruction.instructionIndex) {
return -1;
} else if (instructionIndex == analyzedInstruction.instructionIndex) {
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java
index 9f9e396b..48bf6181 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java
@@ -36,28 +36,18 @@ import com.google.common.base.Suppliers;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
-import org.jf.dexlib2.DexFileFactory;
-import org.jf.dexlib2.DexFileFactory.DexFileNotFound;
-import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
-import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.ClassDef;
-import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.immutable.ImmutableDexFile;
-import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
-import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
public class ClassPath {
@Nonnull private final TypeProto unknownClass;
@@ -70,8 +60,8 @@ public class ClassPath {
/**
* Creates a new ClassPath instance that can load classes from the given providers
*
- * @param classProviders An iterable of ClassProviders. When loading a class, these providers will be searched in
- * order
+ * @param classProviders A varargs array of ClassProviders. When loading a class, these providers will be searched
+ * in order
*/
public ClassPath(ClassProvider... classProviders) throws IOException {
this(Arrays.asList(classProviders), false, NOT_ART);
@@ -82,6 +72,16 @@ public class ClassPath {
*
* @param classProviders An iterable of ClassProviders. When loading a class, these providers will be searched in
* order
+ */
+ public ClassPath(Iterable<ClassProvider> classProviders) throws IOException {
+ this(classProviders, false, NOT_ART);
+ }
+
+ /**
+ * Creates a new ClassPath instance that can load classes from the given providers
+ *
+ * @param classProviders An iterable of ClassProviders. When loading a class, these providers will be searched in
+ * order
* @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by
* default
* @param oatVersion The applicable oat version, or NOT_ART
@@ -114,7 +114,7 @@ public class ClassPath {
private static ClassProvider getBasicClasses() {
// fallbacks for some special classes that we assume are present
- return new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(
+ return new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), ImmutableSet.of(
new ReflectionClassDef(Class.class),
new ReflectionClassDef(Cloneable.class),
new ReflectionClassDef(Object.class),
@@ -164,119 +164,6 @@ public class ClassPath {
return checkPackagePrivateAccess;
}
- @Nonnull
- public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
- int api, boolean experimental) {
- return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17, experimental);
- }
-
- @Nonnull
- public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
- int api, boolean checkPackagePrivateAccess, boolean experimental) {
- List<ClassProvider> providers = Lists.newArrayList();
-
- int oatVersion = NOT_ART;
-
- for (String classPathEntry: classPath) {
- List<? extends DexFile> classPathDexFiles =
- loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
- if (oatVersion == NOT_ART) {
- for (DexFile classPathDexFile: classPathDexFiles) {
- if (classPathDexFile instanceof OatDexFile) {
- oatVersion = ((OatDexFile)classPathDexFile).getOatVersion();
- break;
- }
- }
- }
- for (DexFile classPathDexFile: classPathDexFiles) {
- providers.add(new DexClassProvider(classPathDexFile));
- }
- }
- providers.add(new DexClassProvider(dexFile));
- return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
- }
-
- @Nonnull
- public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
- int api, boolean checkPackagePrivateAccess, boolean experimental,
- int oatVersion) {
- List<ClassProvider> providers = Lists.newArrayList();
-
- for (String classPathEntry: classPath) {
- List<? extends DexFile> classPathDexFiles =
- loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
- for (DexFile classPathDexFile: classPathDexFiles) {
- providers.add(new DexClassProvider(classPathDexFile));
- }
- }
- providers.add(new DexClassProvider(dexFile));
- return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
- }
-
- private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
-
- @Nonnull
- private static List<? extends DexFile> loadClassPathEntry(@Nonnull Iterable<String> classPathDirs,
- @Nonnull String bootClassPathEntry, int api,
- boolean experimental) {
- File rawEntry = new File(bootClassPathEntry);
- // strip off the path - we only care about the filename
- String entryName = rawEntry.getName();
-
- // if it's a dalvik-cache entry, grab the name of the jar/apk
- if (entryName.endsWith("@classes.dex")) {
- Matcher m = dalvikCacheOdexPattern.matcher(entryName);
-
- if (!m.find()) {
- throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", bootClassPathEntry));
- }
-
- entryName = m.group(1);
- }
-
- int extIndex = entryName.lastIndexOf(".");
-
- String baseEntryName;
- if (extIndex == -1) {
- baseEntryName = entryName;
- } else {
- baseEntryName = entryName.substring(0, extIndex);
- }
-
- for (String classPathDir: classPathDirs) {
- String[] extensions;
-
- if (entryName.endsWith(".oat")) {
- extensions = new String[] { ".oat" };
- } else {
- extensions = new String[] { "", ".odex", ".jar", ".apk", ".zip" };
- }
-
- for (String ext: extensions) {
- File file = new File(classPathDir, baseEntryName + ext);
-
- if (file.exists() && file.isFile()) {
- if (!file.canRead()) {
- System.err.println(String.format(
- "warning: cannot open %s for reading. Will continue looking.", file.getPath()));
- } else {
- try {
- return ImmutableList.of(DexFileFactory.loadDexFile(file, api, experimental));
- } catch (DexFileNotFound ex) {
- // ignore and continue
- } catch (MultipleDexFilesException ex) {
- return ex.oatFile.getDexFiles();
- } catch (Exception ex) {
- throw ExceptionWithContext.withContext(ex,
- "Error while reading boot class path entry \"%s\"", bootClassPathEntry);
- }
- }
- }
- }
- }
- throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry);
- }
-
private final Supplier<OdexedFieldInstructionMapper> fieldInstructionMapperSupplier = Suppliers.memoize(
new Supplier<OdexedFieldInstructionMapper>() {
@Override public OdexedFieldInstructionMapper get() {
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPathResolver.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPathResolver.java
new file mode 100644
index 00000000..10daa566
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPathResolver.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.analysis;
+
+import com.beust.jcommander.internal.Sets;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.DexFileFactory.UnsupportedFileTypeException;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
+import org.jf.dexlib2.dexbacked.OatFile;
+import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.MultiDexContainer;
+import org.jf.dexlib2.iface.MultiDexContainer.MultiDexFile;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+public class ClassPathResolver {
+ private final Iterable<String> classPathDirs;
+ private final Opcodes opcodes;
+
+ private final Set<File> loadedFiles = Sets.newHashSet();
+ private final List<ClassProvider> classProviders = Lists.newArrayList();
+
+ /**
+ * Constructs a new ClassPathResolver using a specified list of bootclasspath entries
+ *
+ * @param bootClassPathDirs A list of directories to search for boot classpath entries. Can be empty if all boot
+ * classpath entries are specified as local paths
+ * @param bootClassPathEntries A list of boot classpath entries to load. These can either be local paths, or
+ * device paths (e.g. "/system/framework/framework.jar"). The entry will be interpreted
+ * first as a local path. If not found as a local path, it will be interpreted as a
+ * partial or absolute device path, and will be searched for in bootClassPathDirs
+ * @param extraClassPathEntries A list of additional classpath entries to load. Can be empty. All entries must be
+ * local paths. Device paths are not supported.
+ * @param dexFile The dex file that the classpath will be used to analyze
+ * @throws IOException If any IOException occurs
+ * @throws ResolveException If any classpath entries cannot be loaded for some reason
+ *
+ * If null, a default bootclasspath is used,
+ * depending on the the file type of dexFile and the api level. If empty, no boot
+ * classpath entries will be loaded
+ */
+ public ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nonnull List<String> bootClassPathEntries,
+ @Nonnull List<String> extraClassPathEntries, @Nonnull DexFile dexFile)
+ throws IOException {
+ this(bootClassPathDirs, bootClassPathEntries, extraClassPathEntries, dexFile, true);
+ }
+
+ /**
+ * Constructs a new ClassPathResolver using a default list of bootclasspath entries
+ *
+ * @param bootClassPathDirs A list of directories to search for boot classpath entries
+ * @param extraClassPathEntries A list of additional classpath entries to load. Can be empty. All entries must be
+ * local paths. Device paths are not supported.
+ * @param dexFile The dex file that the classpath will be used to analyze
+ * @throws IOException If any IOException occurs
+ * @throws ResolveException If any classpath entries cannot be loaded for some reason
+ *
+ * If null, a default bootclasspath is used,
+ * depending on the the file type of dexFile and the api level. If empty, no boot
+ * classpath entries will be loaded
+ */
+ public ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nonnull List<String> extraClassPathEntries,
+ @Nonnull DexFile dexFile)
+ throws IOException {
+ this(bootClassPathDirs, null, extraClassPathEntries, dexFile, true);
+ }
+
+ private ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nullable List<String> bootClassPathEntries,
+ @Nonnull List<String> extraClassPathEntries, @Nonnull DexFile dexFile, boolean unused)
+ throws IOException {
+ this.classPathDirs = bootClassPathDirs;
+ opcodes = dexFile.getOpcodes();
+
+ if (bootClassPathEntries == null) {
+ bootClassPathEntries = getDefaultBootClassPath(dexFile, opcodes.api);
+ }
+
+ for (String entry : bootClassPathEntries) {
+ try {
+ loadLocalOrDeviceBootClassPathEntry(entry);
+ } catch (NoDexException ex) {
+ if (entry.endsWith(".jar")) {
+ String odexEntry = entry.substring(0, entry.length() - 4) + ".odex";
+ try {
+ loadLocalOrDeviceBootClassPathEntry(odexEntry);
+ } catch (NoDexException ex2) {
+ throw new ResolveException("Neither %s nor %s contain a dex file", entry, odexEntry);
+ } catch (NotFoundException ex2) {
+ throw new ResolveException(ex);
+ }
+ } else {
+ throw new ResolveException(ex);
+ }
+ } catch (NotFoundException ex) {
+ if (entry.endsWith(".odex")) {
+ String jarEntry = entry.substring(0, entry.length() - 5) + ".jar";
+ try {
+ loadLocalOrDeviceBootClassPathEntry(jarEntry);
+ } catch (NoDexException ex2) {
+ throw new ResolveException("Neither %s nor %s contain a dex file", entry, jarEntry);
+ } catch (NotFoundException ex2) {
+ throw new ResolveException(ex);
+ }
+ } else {
+ throw new ResolveException(ex);
+ }
+ }
+ }
+
+ for (String entry: extraClassPathEntries) {
+ // extra classpath entries must be specified using a local path, so we don't need to do the search through
+ // bootClassPathDirs
+ try {
+ loadLocalClassPathEntry(entry);
+ } catch (NoDexException ex) {
+ throw new ResolveException(ex);
+ }
+ }
+
+ if (dexFile instanceof MultiDexContainer.MultiDexFile) {
+ MultiDexContainer<? extends MultiDexFile> container = ((MultiDexFile)dexFile).getContainer();
+ for (String entry: container.getDexEntryNames()) {
+ classProviders.add(new DexClassProvider(container.getEntry(entry)));
+ }
+ } else {
+ classProviders.add(new DexClassProvider(dexFile));
+ }
+ }
+
+ @Nonnull
+ public List<ClassProvider> getResolvedClassProviders() {
+ return classProviders;
+ }
+
+ private boolean loadLocalClassPathEntry(@Nonnull String entry) throws NoDexException, IOException {
+ File entryFile = new File(entry);
+ if (entryFile.exists() && entryFile.isFile()) {
+ try {
+ loadEntry(entryFile, true);
+ return true;
+ } catch (UnsupportedFileTypeException ex) {
+ throw new ResolveException(ex, "Couldn't load classpath entry %s", entry);
+ }
+ }
+ return false;
+ }
+
+ private void loadLocalOrDeviceBootClassPathEntry(@Nonnull String entry)
+ throws IOException, NoDexException, NotFoundException {
+ // first, see if the entry is a valid local path
+ if (loadLocalClassPathEntry(entry)) {
+ return;
+ }
+
+ // It's not a local path, so let's try to resolve it as a device path, relative to one of the provided
+ // directories
+ List<String> pathComponents = splitDevicePath(entry);
+ Joiner pathJoiner = Joiner.on(File.pathSeparatorChar);
+
+ for (String directory: classPathDirs) {
+ File directoryFile = new File(directory);
+ if (!directoryFile.exists()) {
+ continue;
+ }
+
+ for (int i=0; i<pathComponents.size(); i++) {
+ String partialPath = pathJoiner.join(pathComponents.subList(i, pathComponents.size()));
+ File entryFile = new File(directoryFile, partialPath);
+ if (entryFile.exists() && entryFile.isFile()) {
+ loadEntry(entryFile, true);
+ return;
+ }
+ }
+ }
+
+ throw new NotFoundException("Could not find classpath entry %s", entry);
+ }
+
+ private void loadEntry(@Nonnull File entryFile, boolean loadOatDependencies)
+ throws IOException, NoDexException {
+ if (loadedFiles.contains(entryFile)) {
+ return;
+ }
+
+ MultiDexContainer<? extends DexBackedDexFile> container;
+ try {
+ container = DexFileFactory.loadDexContainer(entryFile, opcodes);
+ } catch (UnsupportedFileTypeException ex) {
+ throw new ResolveException(ex);
+ }
+
+ List<String> entryNames = container.getDexEntryNames();
+
+ if (entryNames.size() == 0) {
+ throw new NoDexException("%s contains no dex file", entryFile);
+ }
+
+ loadedFiles.add(entryFile);
+
+ for (String entryName: entryNames) {
+ classProviders.add(new DexClassProvider(container.getEntry(entryName)));
+ }
+
+ if (loadOatDependencies && container instanceof OatFile) {
+ List<String> oatDependencies = ((OatFile)container).getBootClassPath();
+ if (!oatDependencies.isEmpty()) {
+ try {
+ loadOatDependencies(entryFile.getParentFile(), oatDependencies);
+ } catch (NotFoundException ex) {
+ throw new ResolveException(ex, "Error while loading oat file %s", entryFile);
+ } catch (NoDexException ex) {
+ throw new ResolveException(ex, "Error while loading dependencies for oat file %s", entryFile);
+ }
+ }
+ }
+ }
+
+ @Nonnull
+ private static List<String> splitDevicePath(@Nonnull String path) {
+ return Lists.newArrayList(Splitter.on('/').split(path));
+ }
+
+ private void loadOatDependencies(@Nonnull File directory, @Nonnull List<String> oatDependencies)
+ throws IOException, NoDexException, NotFoundException {
+ // We assume that all oat dependencies are located in the same directory as the oat file
+ for (String oatDependency: oatDependencies) {
+ String oatDependencyName = getFilenameForOatDependency(oatDependency);
+ File file = new File(directory, oatDependencyName);
+ if (!file.exists()) {
+ throw new NotFoundException("Cannot find dependency %s in %s", oatDependencyName, directory);
+ }
+
+ loadEntry(file, false);
+ }
+ }
+
+ @Nonnull
+ private String getFilenameForOatDependency(String oatDependency) {
+ int index = oatDependency.lastIndexOf('/');
+
+ String dependencyLeaf = oatDependency.substring(index+1);
+ if (dependencyLeaf.endsWith(".art")) {
+ return dependencyLeaf.substring(0, dependencyLeaf.length() - 4) + ".oat";
+ }
+ return dependencyLeaf;
+ }
+
+ private static class NotFoundException extends Exception {
+ public NotFoundException(String message, Object... formatArgs) {
+ super(String.format(message, formatArgs));
+ }
+ }
+
+ private static class NoDexException extends Exception {
+ public NoDexException(String message, Object... formatArgs) {
+ super(String.format(message, formatArgs));
+ }
+ }
+
+ /**
+ * An error that occurred while resolving the classpath
+ */
+ public static class ResolveException extends RuntimeException {
+ public ResolveException (String message, Object... formatArgs) {
+ super(String.format(message, formatArgs));
+ }
+
+ public ResolveException (Throwable cause) {
+ super(cause);
+ }
+
+ public ResolveException (Throwable cause, String message, Object... formatArgs) {
+ super(String.format(message, formatArgs), cause);
+ }
+ }
+
+ /**
+ * Returns the default boot class path for the given dex file and api level.
+ */
+ @Nonnull
+ private static List<String> getDefaultBootClassPath(@Nonnull DexFile dexFile, int apiLevel) {
+ if (dexFile instanceof OatFile.OatDexFile) {
+ List<String> bcp = ((OatDexFile)dexFile).getContainer().getBootClassPath();
+ if (!bcp.isEmpty()) {
+ for (int i=0; i<bcp.size(); i++) {
+ String entry = bcp.get(i);
+ if (entry.endsWith(".art")) {
+ bcp.set(i, entry.substring(0, entry.length() - 4) + ".oat");
+ }
+ }
+ return bcp;
+ }
+ return Lists.newArrayList("boot.oat");
+ }
+
+ if (dexFile instanceof DexBackedOdexFile) {
+ return ((DexBackedOdexFile)dexFile).getDependencies();
+ }
+
+ if (apiLevel <= 8) {
+ return Lists.newArrayList(
+ "/system/framework/core.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/android.policy.jar",
+ "/system/framework/services.jar");
+ } else if (apiLevel <= 11) {
+ return Lists.newArrayList(
+ "/system/framework/core.jar",
+ "/system/framework/bouncycastle.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/android.policy.jar",
+ "/system/framework/services.jar",
+ "/system/framework/core-junit.jar");
+ } else if (apiLevel <= 13) {
+ return Lists.newArrayList(
+ "/system/framework/core.jar",
+ "/system/framework/apache-xml.jar",
+ "/system/framework/bouncycastle.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/android.policy.jar",
+ "/system/framework/services.jar",
+ "/system/framework/core-junit.jar");
+ } else if (apiLevel <= 15) {
+ return Lists.newArrayList(
+ "/system/framework/core.jar",
+ "/system/framework/core-junit.jar",
+ "/system/framework/bouncycastle.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/android.policy.jar",
+ "/system/framework/services.jar",
+ "/system/framework/apache-xml.jar",
+ "/system/framework/filterfw.jar");
+ } else if (apiLevel <= 17) {
+ // this is correct as of api 17/4.2.2
+ return Lists.newArrayList(
+ "/system/framework/core.jar",
+ "/system/framework/core-junit.jar",
+ "/system/framework/bouncycastle.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/telephony-common.jar",
+ "/system/framework/mms-common.jar",
+ "/system/framework/android.policy.jar",
+ "/system/framework/services.jar",
+ "/system/framework/apache-xml.jar");
+ } else if (apiLevel <= 18) {
+ return Lists.newArrayList(
+ "/system/framework/core.jar",
+ "/system/framework/core-junit.jar",
+ "/system/framework/bouncycastle.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/telephony-common.jar",
+ "/system/framework/voip-common.jar",
+ "/system/framework/mms-common.jar",
+ "/system/framework/android.policy.jar",
+ "/system/framework/services.jar",
+ "/system/framework/apache-xml.jar");
+ } else if (apiLevel <= 19) {
+ return Lists.newArrayList(
+ "/system/framework/core.jar",
+ "/system/framework/conscrypt.jar",
+ "/system/framework/core-junit.jar",
+ "/system/framework/bouncycastle.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/framework2.jar",
+ "/system/framework/telephony-common.jar",
+ "/system/framework/voip-common.jar",
+ "/system/framework/mms-common.jar",
+ "/system/framework/android.policy.jar",
+ "/system/framework/services.jar",
+ "/system/framework/apache-xml.jar",
+ "/system/framework/webviewchromium.jar");
+ } else if (apiLevel <= 22) {
+ return Lists.newArrayList(
+ "/system/framework/core-libart.jar",
+ "/system/framework/conscrypt.jar",
+ "/system/framework/okhttp.jar",
+ "/system/framework/core-junit.jar",
+ "/system/framework/bouncycastle.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/telephony-common.jar",
+ "/system/framework/voip-common.jar",
+ "/system/framework/ims-common.jar",
+ "/system/framework/mms-common.jar",
+ "/system/framework/android.policy.jar",
+ "/system/framework/apache-xml.jar");
+ } else if (apiLevel <= 23) {
+ return Lists.newArrayList(
+ "/system/framework/core-libart.jar",
+ "/system/framework/conscrypt.jar",
+ "/system/framework/okhttp.jar",
+ "/system/framework/core-junit.jar",
+ "/system/framework/bouncycastle.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/telephony-common.jar",
+ "/system/framework/voip-common.jar",
+ "/system/framework/ims-common.jar",
+ "/system/framework/apache-xml.jar",
+ "/system/framework/org.apache.http.legacy.boot.jar");
+ } else /*if (apiLevel <= 24)*/ {
+ return Lists.newArrayList(
+ "/system/framework/core-oj.jar",
+ "/system/framework/core-libart.jar",
+ "/system/framework/conscrypt.jar",
+ "/system/framework/okhttp.jar",
+ "/system/framework/core-junit.jar",
+ "/system/framework/bouncycastle.jar",
+ "/system/framework/ext.jar",
+ "/system/framework/framework.jar",
+ "/system/framework/telephony-common.jar",
+ "/system/framework/voip-common.jar",
+ "/system/framework/ims-common.jar",
+ "/system/framework/apache-xml.jar",
+ "/system/framework/org.apache.http.legacy.boot.jar");
+ }
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java
index cef683ce..44cc5e24 100644..100755
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java
@@ -31,6 +31,7 @@
package org.jf.dexlib2.analysis;
+import com.google.common.base.Joiner;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
@@ -38,12 +39,10 @@ import com.google.common.collect.*;
import com.google.common.primitives.Ints;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
-import org.jf.dexlib2.iface.ClassDef;
-import org.jf.dexlib2.iface.Field;
-import org.jf.dexlib2.iface.Method;
+import org.jf.dexlib2.base.reference.BaseMethodReference;
+import org.jf.dexlib2.iface.*;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.dexlib2.iface.reference.MethodReference;
-import org.jf.dexlib2.immutable.ImmutableMethod;
import org.jf.dexlib2.util.MethodUtil;
import org.jf.util.AlignmentUtils;
import org.jf.util.ExceptionWithContext;
@@ -52,6 +51,7 @@ import org.jf.util.SparseArray;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
+import java.util.Map.Entry;
/**
* A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields
@@ -122,11 +122,18 @@ public class ClassProto implements TypeProto {
*/
@Nonnull
protected LinkedHashMap<String, ClassDef> getInterfaces() {
- return interfacesSupplier.get();
+ if (!classPath.isArt() || classPath.oatVersion < 72) {
+ return preDefaultMethodInterfaceSupplier.get();
+ } else {
+ return postDefaultMethodInterfaceSupplier.get();
+ }
}
+ /**
+ * This calculates the interfaces in the order required for vtable generation for dalvik and pre-default method ART
+ */
@Nonnull
- private final Supplier<LinkedHashMap<String, ClassDef>> interfacesSupplier =
+ private final Supplier<LinkedHashMap<String, ClassDef>> preDefaultMethodInterfaceSupplier =
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
@Override public LinkedHashMap<String, ClassDef> get() {
Set<String> unresolvedInterfaces = new HashSet<String>(0);
@@ -148,7 +155,8 @@ public class ClassProto implements TypeProto {
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
for (String superInterface: interfaceProto.getInterfaces().keySet()) {
if (!interfaces.containsKey(superInterface)) {
- interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
+ interfaces.put(superInterface,
+ interfaceProto.getInterfaces().get(superInterface));
}
}
if (!interfaceProto.interfacesFullyResolved) {
@@ -158,6 +166,7 @@ public class ClassProto implements TypeProto {
}
}
} catch (UnresolvedClassException ex) {
+ interfaces.put(type, null);
unresolvedInterfaces.add(type);
interfacesFullyResolved = false;
}
@@ -196,6 +205,71 @@ public class ClassProto implements TypeProto {
}
});
+ /**
+ * This calculates the interfaces in the order required for vtable generation for post-default method ART
+ */
+ @Nonnull
+ private final Supplier<LinkedHashMap<String, ClassDef>> postDefaultMethodInterfaceSupplier =
+ Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
+ @Override public LinkedHashMap<String, ClassDef> get() {
+ Set<String> unresolvedInterfaces = new HashSet<String>(0);
+ LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
+
+ String superclass = getSuperclass();
+ if (superclass != null) {
+ ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
+ for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
+ interfaces.put(superclassInterface, null);
+ }
+ if (!superclassProto.interfacesFullyResolved) {
+ unresolvedInterfaces.addAll(superclassProto.getUnresolvedInterfaces());
+ interfacesFullyResolved = false;
+ }
+ }
+
+ try {
+ for (String interfaceType: getClassDef().getInterfaces()) {
+ if (!interfaces.containsKey(interfaceType)) {
+ ClassProto interfaceProto = (ClassProto)classPath.getClass(interfaceType);
+ try {
+ for (Entry<String, ClassDef> entry: interfaceProto.getInterfaces().entrySet()) {
+ if (!interfaces.containsKey(entry.getKey())) {
+ interfaces.put(entry.getKey(), entry.getValue());
+ }
+ }
+ } catch (UnresolvedClassException ex) {
+ interfaces.put(interfaceType, null);
+ unresolvedInterfaces.add(interfaceType);
+ interfacesFullyResolved = false;
+ }
+ if (!interfaceProto.interfacesFullyResolved) {
+ unresolvedInterfaces.addAll(interfaceProto.getUnresolvedInterfaces());
+ interfacesFullyResolved = false;
+ }
+ try {
+ ClassDef interfaceDef = classPath.getClassDef(interfaceType);
+ interfaces.put(interfaceType, interfaceDef);
+ } catch (UnresolvedClassException ex) {
+ interfaces.put(interfaceType, null);
+ unresolvedInterfaces.add(interfaceType);
+ interfacesFullyResolved = false;
+ }
+ }
+ }
+ } catch (UnresolvedClassException ex) {
+ interfaces.put(type, null);
+ unresolvedInterfaces.add(type);
+ interfacesFullyResolved = false;
+ }
+
+ if (unresolvedInterfaces.size() > 0) {
+ ClassProto.this.unresolvedInterfaces = unresolvedInterfaces;
+ }
+
+ return interfaces;
+ }
+ });
+
@Nonnull
protected Set<String> getUnresolvedInterfaces() {
if (unresolvedInterfaces == null) {
@@ -219,7 +293,7 @@ public class ClassProto implements TypeProto {
if (!interfacesFullyResolved) {
throw new UnresolvedClassException("Interfaces for class %s not fully resolved: %s", getType(),
- getUnresolvedInterfaces());
+ Joiner.on(',').join(getUnresolvedInterfaces()));
}
return directInterfaces;
@@ -378,7 +452,10 @@ public class ClassProto implements TypeProto {
}
public int findMethodIndexInVtable(@Nonnull MethodReference method) {
- List<Method> vtable = getVtable();
+ return findMethodIndexInVtable(getVtable(), method);
+ }
+
+ private int findMethodIndexInVtable(@Nonnull List<Method> vtable, MethodReference method) {
for (int i=0; i<vtable.size(); i++) {
Method candidate = vtable.get(i);
if (MethodUtil.methodSignaturesMatch(candidate, method)) {
@@ -391,7 +468,20 @@ public class ClassProto implements TypeProto {
return -1;
}
- @Nonnull SparseArray<FieldReference> getInstanceFields() {
+ private int findMethodIndexInVtableReverse(@Nonnull List<Method> vtable, MethodReference method) {
+ for (int i=vtable.size() - 1; i>=0; i--) {
+ Method candidate = vtable.get(i);
+ if (MethodUtil.methodSignaturesMatch(candidate, method)) {
+ if (!classPath.shouldCheckPackagePrivateAccess() ||
+ AnalyzedMethodUtil.canAccess(this, candidate, true, false, false)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ @Nonnull public SparseArray<FieldReference> getInstanceFields() {
if (classPath.isArt()) {
return artInstanceFieldsSupplier.get();
} else {
@@ -439,10 +529,8 @@ public class ClassProto implements TypeProto {
ClassProto superclass = null;
if (superclassType != null) {
superclass = (ClassProto) classPath.getClass(superclassType);
- if (superclass != null) {
startFieldOffset = superclass.getNextFieldOffset();
}
- }
int fieldIndexMod;
if ((startFieldOffset % 8) == 0) {
@@ -529,14 +617,12 @@ public class ClassProto implements TypeProto {
//add padding to align the wide fields, if needed
if (fieldTypes[i] == WIDE && !gotDouble) {
- if (!gotDouble) {
if (fieldOffset % 8 != 0) {
assert fieldOffset % 8 == 4;
fieldOffset += 4;
}
gotDouble = true;
}
- }
instanceFields.append(fieldOffset, field);
if (fieldTypes[i] == WIDE) {
@@ -573,7 +659,7 @@ public class ClassProto implements TypeProto {
public static FieldGap newFieldGap(int offset, int size, int oatVersion) {
if (oatVersion >= 67) {
return new FieldGap(offset, size) {
- @Override public int compareTo(FieldGap o) {
+ @Override public int compareTo(@Nonnull FieldGap o) {
int result = Ints.compare(o.size, size);
if (result != 0) {
return result;
@@ -583,7 +669,7 @@ public class ClassProto implements TypeProto {
};
} else {
return new FieldGap(offset, size) {
- @Override public int compareTo(FieldGap o) {
+ @Override public int compareTo(@Nonnull FieldGap o) {
int result = Ints.compare(size, o.size);
if (result != 0) {
return result;
@@ -777,12 +863,18 @@ public class ClassProto implements TypeProto {
throw new ExceptionWithContext("Invalid type: %s", type);
}
- @Nonnull List<Method> getVtable() {
- return vtableSupplier.get();
+ @Nonnull public List<Method> getVtable() {
+ if (!classPath.isArt() || classPath.oatVersion < 72) {
+ return preDefaultMethodVtableSupplier.get();
+ } else if (classPath.oatVersion < 87) {
+ return buggyPostDefaultMethodVtableSupplier.get();
+ } else {
+ return postDefaultMethodVtableSupplier.get();
+ }
}
//TODO: check the case when we have a package private method that overrides an interface method
- @Nonnull private final Supplier<List<Method>> vtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
+ @Nonnull private final Supplier<List<Method>> preDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
@Override public List<Method> get() {
List<Method> vtable = Lists.newArrayList();
@@ -811,52 +903,315 @@ public class ClassProto implements TypeProto {
//iterate over the virtual methods in the current class, and only add them when we don't already have the
//method (i.e. if it was implemented by the superclass)
if (!isInterface()) {
- addToVtable(getClassDef().getVirtualMethods(), vtable, true);
+ addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
- // assume that interface method is implemented in the current class, when adding it to vtable
- // otherwise it looks like that method is invoked on an interface, which fails Dalvik's optimization checks
- for (ClassDef interfaceDef: getDirectInterfaces()) {
+ // We use the current class for any vtable method references that we add, rather than the interface, so
+ // we don't end up trying to call invoke-virtual using an interface, which will fail verification
+ Iterable<ClassDef> interfaces = getDirectInterfaces();
+ for (ClassDef interfaceDef: interfaces) {
List<Method> interfaceMethods = Lists.newArrayList();
for (Method interfaceMethod: interfaceDef.getVirtualMethods()) {
- ImmutableMethod method = new ImmutableMethod(
- type,
- interfaceMethod.getName(),
- interfaceMethod.getParameters(),
- interfaceMethod.getReturnType(),
- interfaceMethod.getAccessFlags(),
- interfaceMethod.getAnnotations(),
- interfaceMethod.getImplementation());
- interfaceMethods.add(method);
+ interfaceMethods.add(new ReparentedMethod(interfaceMethod, type));
}
- addToVtable(interfaceMethods, vtable, false);
+ addToVtable(interfaceMethods, vtable, false, true);
}
}
return vtable;
}
+ });
- private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
- @Nonnull List<Method> vtable, boolean replaceExisting) {
- List<? extends Method> methods = Lists.newArrayList(localMethods);
- Collections.sort(methods);
+ /**
+ * This is the vtable supplier for a version of art that had buggy vtable calculation logic. In some cases it can
+ * produce multiple vtable entries for a given virtual method. This supplier duplicates this buggy logic in order to
+ * generate an identical vtable
+ */
+ @Nonnull private final Supplier<List<Method>> buggyPostDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
+ @Override public List<Method> get() {
+ List<Method> vtable = Lists.newArrayList();
+
+ //copy the virtual methods from the superclass
+ String superclassType;
+ try {
+ superclassType = getSuperclass();
+ } catch (UnresolvedClassException ex) {
+ vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
+ vtableFullyResolved = false;
+ return vtable;
+ }
+
+ if (superclassType != null) {
+ ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
+ vtable.addAll(superclass.getVtable());
+
+ // if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by
+ // this class should start, so we just propagate what we can from the parent and hope for the best.
+ if (!superclass.vtableFullyResolved) {
+ vtableFullyResolved = false;
+ return vtable;
+ }
+ }
+
+ //iterate over the virtual methods in the current class, and only add them when we don't already have the
+ //method (i.e. if it was implemented by the superclass)
+ if (!isInterface()) {
+ addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
+
+ List<String> interfaces = Lists.newArrayList(getInterfaces().keySet());
+
+ List<Method> defaultMethods = Lists.newArrayList();
+ List<Method> defaultConflictMethods = Lists.newArrayList();
+ List<Method> mirandaMethods = Lists.newArrayList();
- outer: for (Method virtualMethod: methods) {
- for (int i=0; i<vtable.size(); i++) {
- Method superMethod = vtable.get(i);
- if (MethodUtil.methodSignaturesMatch(superMethod, virtualMethod)) {
+ final HashMap<MethodReference, Integer> methodOrder = Maps.newHashMap();
+
+ for (int i=interfaces.size()-1; i>=0; i--) {
+ String interfaceType = interfaces.get(i);
+ ClassDef interfaceDef = classPath.getClassDef(interfaceType);
+
+ for (Method interfaceMethod : interfaceDef.getVirtualMethods()) {
+
+ int vtableIndex = findMethodIndexInVtableReverse(vtable, interfaceMethod);
+ Method oldVtableMethod = null;
+ if (vtableIndex >= 0) {
+ oldVtableMethod = vtable.get(vtableIndex);
+ }
+
+ for (int j=0; j<vtable.size(); j++) {
+ Method candidate = vtable.get(j);
+ if (MethodUtil.methodSignaturesMatch(candidate, interfaceMethod)) {
if (!classPath.shouldCheckPackagePrivateAccess() ||
- AnalyzedMethodUtil.canAccess(ClassProto.this, superMethod, true, false, false)) {
- if (replaceExisting) {
- vtable.set(i, virtualMethod);
+ AnalyzedMethodUtil.canAccess(ClassProto.this, candidate, true, false, false)) {
+ if (interfaceMethodOverrides(interfaceMethod, candidate)) {
+ vtable.set(j, interfaceMethod);
+ }
+ }
+ }
+ }
+
+ if (vtableIndex >= 0) {
+ if (!isOverridableByDefaultMethod(vtable.get(vtableIndex))) {
+ continue;
+ }
+ }
+
+ int defaultMethodIndex = findMethodIndexInVtable(defaultMethods, interfaceMethod);
+
+ if (defaultMethodIndex >= 0) {
+ if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
+ ClassProto existingInterface = (ClassProto)classPath.getClass(
+ defaultMethods.get(defaultMethodIndex).getDefiningClass());
+ if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
+ Method removedMethod = defaultMethods.remove(defaultMethodIndex);
+ defaultConflictMethods.add(removedMethod);
+ }
}
- continue outer;
+ continue;
}
+
+ int defaultConflictMethodIndex = findMethodIndexInVtable(
+ defaultConflictMethods, interfaceMethod);
+ if (defaultConflictMethodIndex >= 0) {
+ // There's already a matching method in the conflict list, we don't need to do
+ // anything else
+ continue;
+ }
+
+ int mirandaMethodIndex = findMethodIndexInVtable(mirandaMethods, interfaceMethod);
+
+ if (mirandaMethodIndex >= 0) {
+ if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
+
+ ClassProto existingInterface = (ClassProto)classPath.getClass(
+ mirandaMethods.get(mirandaMethodIndex).getDefiningClass());
+ if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
+ Method oldMethod = mirandaMethods.remove(mirandaMethodIndex);
+ int methodOrderValue = methodOrder.get(oldMethod);
+ methodOrder.put(interfaceMethod, methodOrderValue);
+ defaultMethods.add(interfaceMethod);
+ }
+ }
+ continue;
+ }
+
+ if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
+ if (oldVtableMethod != null) {
+ if (!interfaceMethodOverrides(interfaceMethod, oldVtableMethod)) {
+ continue;
+ }
+ }
+ defaultMethods.add(interfaceMethod);
+ methodOrder.put(interfaceMethod, methodOrder.size());
+ } else {
+ // TODO: do we need to check interfaceMethodOverrides here?
+ if (oldVtableMethod == null) {
+ mirandaMethods.add(interfaceMethod);
+ methodOrder.put(interfaceMethod, methodOrder.size());
+ }
+ }
+ }
+ }
+
+ Comparator<MethodReference> comparator = new Comparator<MethodReference>() {
+ @Override public int compare(MethodReference o1, MethodReference o2) {
+ return Ints.compare(methodOrder.get(o1), methodOrder.get(o2));
+ }
+ };
+
+ // The methods should be in the same order within each list as they were iterated over.
+ // They can be misordered if, e.g. a method was originally added to the default list, but then moved
+ // to the conflict list.
+ Collections.sort(mirandaMethods, comparator);
+ Collections.sort(defaultMethods, comparator);
+ Collections.sort(defaultConflictMethods, comparator);
+
+ vtable.addAll(mirandaMethods);
+ vtable.addAll(defaultMethods);
+ vtable.addAll(defaultConflictMethods);
+ }
+ return vtable;
+ }
+ });
+
+ @Nonnull private final Supplier<List<Method>> postDefaultMethodVtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
+ @Override public List<Method> get() {
+ List<Method> vtable = Lists.newArrayList();
+
+ //copy the virtual methods from the superclass
+ String superclassType;
+ try {
+ superclassType = getSuperclass();
+ } catch (UnresolvedClassException ex) {
+ vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
+ vtableFullyResolved = false;
+ return vtable;
+ }
+
+ if (superclassType != null) {
+ ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
+ vtable.addAll(superclass.getVtable());
+
+ // if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by
+ // this class should start, so we just propagate what we can from the parent and hope for the best.
+ if (!superclass.vtableFullyResolved) {
+ vtableFullyResolved = false;
+ return vtable;
+ }
+ }
+
+ //iterate over the virtual methods in the current class, and only add them when we don't already have the
+ //method (i.e. if it was implemented by the superclass)
+ if (!isInterface()) {
+ addToVtable(getClassDef().getVirtualMethods(), vtable, true, true);
+
+ Iterable<ClassDef> interfaces = Lists.reverse(Lists.newArrayList(getDirectInterfaces()));
+
+ List<Method> defaultMethods = Lists.newArrayList();
+ List<Method> defaultConflictMethods = Lists.newArrayList();
+ List<Method> mirandaMethods = Lists.newArrayList();
+
+ final HashMap<MethodReference, Integer> methodOrder = Maps.newHashMap();
+
+ for (ClassDef interfaceDef: interfaces) {
+ for (Method interfaceMethod : interfaceDef.getVirtualMethods()) {
+
+ int vtableIndex = findMethodIndexInVtable(vtable, interfaceMethod);
+
+ if (vtableIndex >= 0) {
+ if (interfaceMethodOverrides(interfaceMethod, vtable.get(vtableIndex))) {
+ vtable.set(vtableIndex, interfaceMethod);
+ }
+ } else {
+ int defaultMethodIndex = findMethodIndexInVtable(defaultMethods, interfaceMethod);
+
+ if (defaultMethodIndex >= 0) {
+ if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
+ ClassProto existingInterface = (ClassProto)classPath.getClass(
+ defaultMethods.get(defaultMethodIndex).getDefiningClass());
+ if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
+ Method removedMethod = defaultMethods.remove(defaultMethodIndex);
+ defaultConflictMethods.add(removedMethod);
+ }
+ }
+ continue;
+ }
+
+ int defaultConflictMethodIndex = findMethodIndexInVtable(
+ defaultConflictMethods, interfaceMethod);
+ if (defaultConflictMethodIndex >= 0) {
+ // There's already a matching method in the conflict list, we don't need to do
+ // anything else
+ continue;
+ }
+
+ int mirandaMethodIndex = findMethodIndexInVtable(mirandaMethods, interfaceMethod);
+
+ if (mirandaMethodIndex >= 0) {
+ if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
+
+ ClassProto existingInterface = (ClassProto)classPath.getClass(
+ mirandaMethods.get(mirandaMethodIndex).getDefiningClass());
+ if (!existingInterface.implementsInterface(interfaceMethod.getDefiningClass())) {
+ Method oldMethod = mirandaMethods.remove(mirandaMethodIndex);
+ int methodOrderValue = methodOrder.get(oldMethod);
+ methodOrder.put(interfaceMethod, methodOrderValue);
+ defaultMethods.add(interfaceMethod);
+ }
+ }
+ continue;
+ }
+
+ if (!AccessFlags.ABSTRACT.isSet(interfaceMethod.getAccessFlags())) {
+ defaultMethods.add(interfaceMethod);
+ methodOrder.put(interfaceMethod, methodOrder.size());
+ } else {
+ mirandaMethods.add(interfaceMethod);
+ methodOrder.put(interfaceMethod, methodOrder.size());
+ }
+ }
+ }
+ }
+
+ Comparator<MethodReference> comparator = new Comparator<MethodReference>() {
+ @Override public int compare(MethodReference o1, MethodReference o2) {
+ return Ints.compare(methodOrder.get(o1), methodOrder.get(o2));
}
+ };
+
+ // The methods should be in the same order within each list as they were iterated over.
+ // They can be misordered if, e.g. a method was originally added to the default list, but then moved
+ // to the conflict list.
+ Collections.sort(defaultMethods, comparator);
+ Collections.sort(defaultConflictMethods, comparator);
+ Collections.sort(mirandaMethods, comparator);
+ addToVtable(defaultMethods, vtable, false, false);
+ addToVtable(defaultConflictMethods, vtable, false, false);
+ addToVtable(mirandaMethods, vtable, false, false);
+ }
+ return vtable;
+ }
+ });
+
+ private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, @Nonnull List<Method> vtable,
+ boolean replaceExisting, boolean sort) {
+ if (sort) {
+ ArrayList<Method> methods = Lists.newArrayList(localMethods);
+ Collections.sort(methods);
+ localMethods = methods;
+ }
+
+ for (Method virtualMethod: localMethods) {
+ int vtableIndex = findMethodIndexInVtable(vtable, virtualMethod);
+
+ if (vtableIndex >= 0) {
+ if (replaceExisting) {
+ vtable.set(vtableIndex, virtualMethod);
}
+ } else {
// we didn't find an equivalent method, so add it as a new entry
vtable.add(virtualMethod);
}
}
- });
+ }
private static byte getFieldType(@Nonnull FieldReference field) {
switch (field.getType().charAt(0)) {
@@ -870,4 +1225,68 @@ public class ClassProto implements TypeProto {
return 2; //OTHER
}
}
+
+ private boolean isOverridableByDefaultMethod(@Nonnull Method method) {
+ ClassProto classProto = (ClassProto)classPath.getClass(method.getDefiningClass());
+ return classProto.isInterface();
+ }
+
+ /**
+ * Checks if the interface method overrides the virtual or interface method2
+ * @param method A Method from an interface
+ * @param method2 A Method from an interface or a class
+ * @return true if the interface method overrides the virtual or interface method2
+ */
+ private boolean interfaceMethodOverrides(@Nonnull Method method, @Nonnull Method method2) {
+ ClassProto classProto = (ClassProto)classPath.getClass(method2.getDefiningClass());
+
+ if (classProto.isInterface()) {
+ ClassProto targetClassProto = (ClassProto)classPath.getClass(method.getDefiningClass());
+ return targetClassProto.implementsInterface(method2.getDefiningClass());
+ } else {
+ return false;
+ }
+ }
+
+ static class ReparentedMethod extends BaseMethodReference implements Method {
+ private final Method method;
+ private final String definingClass;
+
+ public ReparentedMethod(Method method, String definingClass) {
+ this.method = method;
+ this.definingClass = definingClass;
+ }
+
+ @Nonnull @Override public String getDefiningClass() {
+ return definingClass;
+ }
+
+ @Nonnull @Override public String getName() {
+ return method.getName();
+ }
+
+ @Nonnull @Override public List<? extends CharSequence> getParameterTypes() {
+ return method.getParameterTypes();
+ }
+
+ @Nonnull @Override public String getReturnType() {
+ return method.getReturnType();
+ }
+
+ @Nonnull @Override public List<? extends MethodParameter> getParameters() {
+ return method.getParameters();
+ }
+
+ @Override public int getAccessFlags() {
+ return method.getAccessFlags();
+ }
+
+ @Nonnull @Override public Set<? extends Annotation> getAnnotations() {
+ return method.getAnnotations();
+ }
+
+ @Nullable @Override public MethodImplementation getImplementation() {
+ return method.getImplementation();
+ }
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java
deleted file mode 100644
index 2bb3e492..00000000
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2013, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.jf.dexlib2.analysis;
-
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import org.apache.commons.cli.*;
-import org.jf.dexlib2.DexFileFactory;
-import org.jf.dexlib2.dexbacked.DexBackedDexFile;
-import org.jf.dexlib2.iface.ClassDef;
-import org.jf.dexlib2.iface.Field;
-import org.jf.dexlib2.iface.Method;
-import org.jf.dexlib2.iface.reference.FieldReference;
-import org.jf.util.ConsoleUtil;
-import org.jf.util.SparseArray;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
-public class DumpFields {
- private static final Options options;
-
- static {
- options = new Options();
- buildOptions();
- }
-
- public static void main(String[] args) {
- CommandLineParser parser = new PosixParser();
- CommandLine commandLine;
-
- try {
- commandLine = parser.parse(options, args);
- } catch (ParseException ex) {
- usage();
- return;
- }
-
- String[] remainingArgs = commandLine.getArgs();
-
- Option[] parsedOptions = commandLine.getOptions();
- ArrayList<String> bootClassPathDirs = Lists.newArrayList();
- String outFile = "fields.txt";
- int apiLevel = 15;
- boolean experimental = false;
-
- for (int i=0; i<parsedOptions.length; i++) {
- Option option = parsedOptions[i];
- String opt = option.getOpt();
-
- switch (opt.charAt(0)) {
- case 'd':
- bootClassPathDirs.add(option.getValue());
- break;
- case 'o':
- outFile = option.getValue();
- break;
- case 'a':
- apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
- break;
- case 'X':
- experimental = true;
- break;
- default:
- assert false;
- }
- }
-
- if (remainingArgs.length != 1) {
- usage();
- return;
- }
-
- String inputDexFileName = remainingArgs[0];
-
- File dexFileFile = new File(inputDexFileName);
- if (!dexFileFile.exists()) {
- System.err.println("Can't find the file " + inputDexFileName);
- System.exit(1);
- }
-
- try {
- DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel, experimental);
- Iterable<String> bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar");
- ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental);
- FileOutputStream outStream = new FileOutputStream(outFile);
-
- for (ClassDef classDef: dexFile.getClasses()) {
- ClassProto classProto = (ClassProto) classPath.getClass(classDef);
- SparseArray<FieldReference> fields = classProto.getInstanceFields();
- String className = "Class " + classDef.getType() + " : " + fields.size() + " instance fields\n";
- outStream.write(className.getBytes());
- for (int i=0;i<fields.size();i++) {
- String field = fields.keyAt(i) + ":" + fields.valueAt(i).getType() + " " + fields.valueAt(i).getName() + "\n";
- outStream.write(field.getBytes());
- }
- outStream.write("\n".getBytes());
- }
- outStream.close();
- } catch (IOException ex) {
- System.out.println("IOException thrown when trying to open a dex file or write out vtables: " + ex);
- }
-
- }
-
- /**
- * Prints the usage message.
- */
- private static void usage() {
- int consoleWidth = ConsoleUtil.getConsoleWidth();
- if (consoleWidth <= 0) {
- consoleWidth = 80;
- }
-
- System.out.println("java -cp baksmali.jar org.jf.dexlib2.analysis.DumpFields -d path/to/framework/jar/files <dex-file>");
- }
-
- private static void buildOptions() {
- Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
- .withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
- "directory")
- .hasArg()
- .withArgName("DIR")
- .create("d");
-
- Option outputFileOption = OptionBuilder.withLongOpt("out-file")
- .withDescription("output file")
- .hasArg()
- .withArgName("FILE")
- .create("o");
-
- Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
- .withDescription("The numeric api-level of the file being disassembled. If not " +
- "specified, it defaults to 15 (ICS).")
- .hasArg()
- .withArgName("API_LEVEL")
- .create("a");
-
- Option experimentalOption = OptionBuilder.withLongOpt("experimental")
- .withDescription("Enable dumping experimental opcodes, that aren't necessarily " +
- "supported by the android runtime yet.")
- .create("X");
-
- options.addOption(classPathDirOption);
- options.addOption(outputFileOption);
- options.addOption(apiLevelOption);
- options.addOption(experimentalOption);
- }
-}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java
deleted file mode 100644
index 193c0d39..00000000
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright 2013, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.jf.dexlib2.analysis;
-
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import org.apache.commons.cli.*;
-import org.jf.dexlib2.DexFileFactory;
-import org.jf.dexlib2.dexbacked.DexBackedDexFile;
-import org.jf.dexlib2.iface.ClassDef;
-import org.jf.dexlib2.iface.Method;
-import org.jf.util.ConsoleUtil;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class DumpVtables {
- private static final Options options;
-
- static {
- options = new Options();
- buildOptions();
- }
-
- public static void main(String[] args) {
- CommandLineParser parser = new PosixParser();
- CommandLine commandLine;
-
- try {
- commandLine = parser.parse(options, args);
- } catch (ParseException ex) {
- usage();
- return;
- }
-
- String[] remainingArgs = commandLine.getArgs();
-
- Option[] parsedOptions = commandLine.getOptions();
- ArrayList<String> bootClassPathDirs = Lists.newArrayList();
- String outFile = "vtables.txt";
- int apiLevel = 15;
- boolean experimental = false;
-
- for (int i=0; i<parsedOptions.length; i++) {
- Option option = parsedOptions[i];
- String opt = option.getOpt();
-
- switch (opt.charAt(0)) {
- case 'd':
- bootClassPathDirs.add(option.getValue());
- break;
- case 'o':
- outFile = option.getValue();
- break;
- case 'a':
- apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
- break;
- case 'X':
- experimental = true;
- break;
- default:
- assert false;
- }
- }
-
- if (remainingArgs.length != 1) {
- usage();
- return;
- }
-
- String inputDexFileName = remainingArgs[0];
-
- File dexFileFile = new File(inputDexFileName);
- if (!dexFileFile.exists()) {
- System.err.println("Can't find the file " + inputDexFileName);
- System.exit(1);
- }
-
- try {
- DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel, experimental);
- Iterable<String> bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar");
- ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental);
- FileOutputStream outStream = new FileOutputStream(outFile);
-
- for (ClassDef classDef: dexFile.getClasses()) {
- ClassProto classProto = (ClassProto) classPath.getClass(classDef);
- List<Method> methods = classProto.getVtable();
- String className = "Class " + classDef.getType() + " extends " + classDef.getSuperclass() + " : " + methods.size() + " methods\n";
- outStream.write(className.getBytes());
- for (int i=0;i<methods.size();i++) {
- Method method = methods.get(i);
-
- String methodString = i + ":" + method.getDefiningClass() + "->" + method.getName() + "(";
- for (CharSequence parameter: method.getParameterTypes()) {
- methodString += parameter;
- }
- methodString += ")" + method.getReturnType() + "\n";
- outStream.write(methodString.getBytes());
- }
- outStream.write("\n".getBytes());
- }
- outStream.close();
- } catch (IOException ex) {
- System.out.println("IOException thrown when trying to open a dex file or write out vtables: " + ex);
- }
-
- }
-
- /**
- * Prints the usage message.
- */
- private static void usage() {
- int consoleWidth = ConsoleUtil.getConsoleWidth();
- if (consoleWidth <= 0) {
- consoleWidth = 80;
- }
-
- System.out.println("java -cp baksmali.jar org.jf.dexlib2.analysis.DumpVtables -d path/to/framework/jar/files <dex-file>");
- }
-
- private static void buildOptions() {
- Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
- .withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
- "directory")
- .hasArg()
- .withArgName("DIR")
- .create("d");
-
- Option outputFileOption = OptionBuilder.withLongOpt("out-file")
- .withDescription("output file")
- .hasArg()
- .withArgName("FILE")
- .create("o");
-
- Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
- .withDescription("The numeric api-level of the file being disassembled. If not " +
- "specified, it defaults to 15 (ICS).")
- .hasArg()
- .withArgName("API_LEVEL")
- .create("a");
-
- Option experimentalOption = OptionBuilder.withLongOpt("experimental")
- .withDescription("Enable dumping experimental opcodes, that aren't necessarily " +
- "supported by the android runtime yet.")
- .create("X");
-
- options.addOption(classPathDirOption);
- options.addOption(outputFileOption);
- options.addOption(apiLevelOption);
- options.addOption(experimentalOption);
- }
-}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java
index b7a15a01..7a51c96c 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java
@@ -36,6 +36,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.Opcode;
+import org.jf.dexlib2.base.reference.BaseMethodReference;
import org.jf.dexlib2.iface.*;
import org.jf.dexlib2.iface.instruction.*;
import org.jf.dexlib2.iface.instruction.formats.*;
@@ -89,10 +90,10 @@ public class MethodAnalyzer {
@Nullable private AnalysisException analysisException = null;
- //This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the
- //register types for this instruction to the parameter types, in order to have them propagate to all of its
- //successors, e.g. the first real instruction, the first instructions in any exception handlers covering the first
- //instruction, etc.
+ // This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the
+ // register types for this instruction to the parameter types, in order to have them propagate to all of its
+ // successors, e.g. the first real instruction, the first instructions in any exception handlers covering the first
+ // instruction, etc.
private final AnalyzedInstruction startOfMethod;
public MethodAnalyzer(@Nonnull ClassPath classPath, @Nonnull Method method,
@@ -110,27 +111,16 @@ public class MethodAnalyzer {
this.methodImpl = methodImpl;
- //override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't
- //have to handle the case this special case of instruction being null, in the main class
- startOfMethod = new AnalyzedInstruction(this, null, -1, methodImpl.getRegisterCount()) {
- public boolean setsRegister() {
- return false;
- }
-
- @Override
- public boolean setsWideRegister() {
- return false;
- }
-
- @Override
- public boolean setsRegister(int registerNumber) {
- return false;
+ // Override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't
+ // have to handle the case this special case of instruction being null, in the main class
+ startOfMethod = new AnalyzedInstruction(this, new ImmutableInstruction10x(Opcode.NOP), -1, methodImpl.getRegisterCount()) {
+ @Override protected boolean addPredecessor(AnalyzedInstruction predecessor) {
+ throw new UnsupportedOperationException();
}
- @Override
- public int getDestinationRegister() {
- assert false;
- return -1;
+ @Override @Nonnull
+ public RegisterType getPredecessorRegisterType(@Nonnull AnalyzedInstruction predecessor, int registerNumber) {
+ throw new UnsupportedOperationException();
}
};
@@ -141,6 +131,7 @@ public class MethodAnalyzer {
analyze();
}
+ @Nonnull
public ClassPath getClassPath() {
return classPath;
}
@@ -362,6 +353,7 @@ public class MethodAnalyzer {
private void overridePredecessorRegisterTypeAndPropagateChanges(
@Nonnull AnalyzedInstruction analyzedInstruction, @Nonnull AnalyzedInstruction predecessor,
int registerNumber, @Nonnull RegisterType registerType) {
+
BitSet changedInstructions = new BitSet(analyzedInstructions.size());
if (!analyzedInstruction.overridePredecessorRegisterType(
@@ -383,6 +375,28 @@ public class MethodAnalyzer {
}
}
+ private void initializeRefAndPropagateChanges(@Nonnull AnalyzedInstruction analyzedInstruction,
+ int registerNumber, @Nonnull RegisterType registerType) {
+
+ BitSet changedInstructions = new BitSet(analyzedInstructions.size());
+
+ if (!analyzedInstruction.setPostRegisterType(registerNumber, registerType)) {
+ return;
+ }
+
+ propagateRegisterToSuccessors(analyzedInstruction, registerNumber, changedInstructions, false);
+
+ propagateChanges(changedInstructions, registerNumber, false);
+
+ if (registerType.category == RegisterType.LONG_LO) {
+ checkWidePair(registerNumber, analyzedInstruction);
+ setPostRegisterTypeAndPropagateChanges(analyzedInstruction, registerNumber+1, RegisterType.LONG_HI_TYPE);
+ } else if (registerType.category == RegisterType.DOUBLE_LO) {
+ checkWidePair(registerNumber, analyzedInstruction);
+ setPostRegisterTypeAndPropagateChanges(analyzedInstruction, registerNumber+1, RegisterType.DOUBLE_HI_TYPE);
+ }
+ }
+
private void setPostRegisterTypeAndPropagateChanges(@Nonnull AnalyzedInstruction analyzedInstruction,
int registerNumber, @Nonnull RegisterType registerType) {
@@ -1176,32 +1190,46 @@ public class MethodAnalyzer {
setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, castRegisterType);
}
- static boolean canNarrowAfterInstanceOf(AnalyzedInstruction analyzedInstanceOfInstruction,
- AnalyzedInstruction analyzedIfInstruction, ClassPath classPath) {
+ public static boolean isNotWideningConversion(RegisterType originalType, RegisterType newType) {
+ if (originalType.type == null || newType.type == null) {
+ return true;
+ }
+ if (originalType.type.isInterface()) {
+ return newType.type.implementsInterface(originalType.type.getType());
+ } else {
+ TypeProto commonSuperclass = newType.type.getCommonSuperclass(originalType.type);
+ if (commonSuperclass.getType().equals(originalType.type.getType())) {
+ return true;
+ }
+ if (commonSuperclass.getType().equals(newType.type.getType())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean canPropagateTypeAfterInstanceOf(AnalyzedInstruction analyzedInstanceOfInstruction,
+ AnalyzedInstruction analyzedIfInstruction, ClassPath classPath) {
+ if (!classPath.isArt()) {
+ return false;
+ }
+
Instruction ifInstruction = analyzedIfInstruction.instruction;
- assert analyzedIfInstruction.instruction != null;
if (((Instruction21t)ifInstruction).getRegisterA() == analyzedInstanceOfInstruction.getDestinationRegister()) {
Reference reference = ((Instruction22c)analyzedInstanceOfInstruction.getInstruction()).getReference();
RegisterType registerType = RegisterType.getRegisterType(classPath, (TypeReference)reference);
- if (registerType.type != null && !registerType.type.isInterface()) {
- int objectRegister = ((TwoRegisterInstruction)analyzedInstanceOfInstruction.getInstruction())
- .getRegisterB();
+ try {
+ if (registerType.type != null && !registerType.type.isInterface()) {
+ int objectRegister = ((TwoRegisterInstruction)analyzedInstanceOfInstruction.getInstruction())
+ .getRegisterB();
- RegisterType originalType = analyzedIfInstruction.getPreInstructionRegisterType(objectRegister);
+ RegisterType originalType = analyzedIfInstruction.getPreInstructionRegisterType(objectRegister);
- if (originalType.type != null) {
- // Only override if we're going from an interface to a class, or are going to a narrower class
- if (originalType.type.isInterface()) {
- return true;
- } else {
- TypeProto commonSuperclass = registerType.type.getCommonSuperclass(originalType.type);
- // only if it's a narrowing conversion
- if (commonSuperclass.getType().equals(originalType.type.getType())) {
- return true;
- }
- }
+ return isNotWideningConversion(originalType, registerType);
}
+ } catch (UnresolvedClassException ex) {
+ return false;
}
}
return false;
@@ -1210,21 +1238,16 @@ public class MethodAnalyzer {
/**
* Art uses a peephole optimization for an if-eqz or if-nez that occur immediately after an instance-of. It will
* narrow the type if possible, and then NOP out any corresponding check-cast instruction later on
- *
- * TODO: Is this still safe to do even for dalvik odexes? I think it should be..
*/
private void analyzeIfEqzNez(@Nonnull AnalyzedInstruction analyzedInstruction) {
- int instructionIndex = analyzedInstruction.getInstructionIndex();
- if (instructionIndex > 0) {
- AnalyzedInstruction prevAnalyzedInstruction = analyzedInstructions.valueAt(instructionIndex - 1);
- if (prevAnalyzedInstruction.instruction != null &&
- prevAnalyzedInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF) {
- if (canNarrowAfterInstanceOf(prevAnalyzedInstruction, analyzedInstruction, classPath)) {
- // Propagate the original type to the failing branch, and the new type to the successful branch
- int narrowingRegister = ((Instruction22c)prevAnalyzedInstruction.instruction).getRegisterB();
- RegisterType originalType = analyzedInstruction.getPreInstructionRegisterType(narrowingRegister);
- RegisterType newType = RegisterType.getRegisterType(classPath,
- (TypeReference)((Instruction22c)prevAnalyzedInstruction.instruction).getReference());
+ if (classPath.isArt()) {
+ int instructionIndex = analyzedInstruction.getInstructionIndex();
+ if (instructionIndex > 0) {
+ if (analyzedInstruction.getPredecessorCount() != 1) {
+ return;
+ }
+ AnalyzedInstruction prevAnalyzedInstruction = analyzedInstruction.getPredecessors().first();
+ if (prevAnalyzedInstruction.instruction.getOpcode() == Opcode.INSTANCE_OF) {
AnalyzedInstruction fallthroughInstruction = analyzedInstructions.valueAt(
analyzedInstruction.getInstructionIndex() + 1);
@@ -1233,16 +1256,25 @@ public class MethodAnalyzer {
((Instruction21t)analyzedInstruction.instruction).getCodeOffset();
AnalyzedInstruction branchInstruction = analyzedInstructions.get(nextAddress);
- if (analyzedInstruction.instruction.getOpcode() == Opcode.IF_EQZ) {
- overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
- narrowingRegister, newType);
- overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
- narrowingRegister, originalType);
- } else {
- overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction, analyzedInstruction,
- narrowingRegister, originalType);
- overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
- narrowingRegister, newType);
+ int narrowingRegister = ((Instruction22c)prevAnalyzedInstruction.instruction).getRegisterB();
+ RegisterType originalType = analyzedInstruction.getPreInstructionRegisterType(narrowingRegister);
+
+ Instruction22c instanceOfInstruction = (Instruction22c)prevAnalyzedInstruction.instruction;
+ RegisterType newType = RegisterType.getRegisterType(classPath,
+ (TypeReference)instanceOfInstruction.getReference());
+
+ for (int register : analyzedInstruction.getSetRegisters()) {
+ if (analyzedInstruction.instruction.getOpcode() == Opcode.IF_EQZ) {
+ overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction,
+ analyzedInstruction, register, newType);
+ overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
+ register, originalType);
+ } else {
+ overridePredecessorRegisterTypeAndPropagateChanges(fallthroughInstruction,
+ analyzedInstruction, register, originalType);
+ overridePredecessorRegisterTypeAndPropagateChanges(branchInstruction, analyzedInstruction,
+ register, newType);
+ }
}
}
}
@@ -1380,44 +1412,32 @@ public class MethodAnalyzer {
}
private void analyzeInvokeDirectCommon(@Nonnull AnalyzedInstruction analyzedInstruction, int objectRegister) {
- //the only time that an invoke instruction changes a register type is when using invoke-direct on a
- //constructor (<init>) method, which changes the uninitialized reference (and any register that the same
- //uninit reference has been copied to) to an initialized reference
-
- ReferenceInstruction instruction = (ReferenceInstruction)analyzedInstruction.instruction;
-
- MethodReference methodReference = (MethodReference)instruction.getReference();
-
- if (!methodReference.getName().equals("<init>")) {
- return;
- }
-
- RegisterType objectRegisterType = analyzedInstruction.getPreInstructionRegisterType(objectRegister);
-
- if (objectRegisterType.category != RegisterType.UNINIT_REF &&
- objectRegisterType.category != RegisterType.UNINIT_THIS) {
- return;
- }
+ // This handles the case of invoking a constructor on an uninitialized reference. This propagates the
+ // initialized type for the object register, and also any known aliased registers.
+ //
+ // In some cases, unrelated uninitialized references may not have been propagated past this instruction. This
+ // happens when propagating those types and the type of object register of this instruction isn't known yet.
+ // In this case, we can't determine if the uninitialized reference being propagated in an alias of the object
+ // register, so we don't stop propagation.
+ //
+ // We check for any of these unpropagated uninitialized references here and propagate them.
+ if (analyzedInstruction.isInvokeInit()) {
+ RegisterType uninitRef = analyzedInstruction.getPreInstructionRegisterType(objectRegister);
+ if (uninitRef.category != RegisterType.UNINIT_REF && uninitRef.category != RegisterType.UNINIT_THIS) {
+ assert analyzedInstruction.getSetRegisters().isEmpty();
+ return;
+ }
- setPostRegisterTypeAndPropagateChanges(analyzedInstruction, objectRegister,
- RegisterType.getRegisterType(RegisterType.REFERENCE, objectRegisterType.type));
+ RegisterType initRef = RegisterType.getRegisterType(RegisterType.REFERENCE, uninitRef.type);
- for (int i=0; i<analyzedInstruction.postRegisterMap.length; i++) {
- RegisterType postInstructionRegisterType = analyzedInstruction.postRegisterMap[i];
- if (postInstructionRegisterType.category == RegisterType.UNKNOWN) {
- RegisterType preInstructionRegisterType =
- analyzedInstruction.getPreInstructionRegisterType(i);
+ for (int register: analyzedInstruction.getSetRegisters()) {
+ RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(register);
- if (preInstructionRegisterType.category == RegisterType.UNINIT_REF ||
- preInstructionRegisterType.category == RegisterType.UNINIT_THIS) {
- RegisterType registerType;
- if (preInstructionRegisterType.equals(objectRegisterType)) {
- registerType = analyzedInstruction.postRegisterMap[objectRegister];
- } else {
- registerType = preInstructionRegisterType;
- }
-
- setPostRegisterTypeAndPropagateChanges(analyzedInstruction, i, registerType);
+ if (registerType == uninitRef) {
+ setPostRegisterTypeAndPropagateChanges(analyzedInstruction, register, initRef);
+ } else {
+ // This is unrelated uninitialized reference. propagate it as-is
+ setPostRegisterTypeAndPropagateChanges(analyzedInstruction, register, registerType);
}
}
}
@@ -1695,13 +1715,13 @@ public class MethodAnalyzer {
// fieldClass is now the first accessible class found. Now. we need to make sure that the field is
// actually valid for this class
- resolvedField = classPath.getClass(fieldClass.getType()).getFieldByOffset(fieldOffset);
- if (resolvedField == null) {
+ FieldReference newResolvedField = classPath.getClass(fieldClass.getType()).getFieldByOffset(fieldOffset);
+ if (newResolvedField == null) {
throw new ExceptionWithContext("Couldn't find accessible class while resolving field %s",
ReferenceUtil.getShortFieldDescriptor(resolvedField));
}
- resolvedField = new ImmutableFieldReference(fieldClass.getType(), resolvedField.getName(),
- resolvedField.getType());
+ resolvedField = new ImmutableFieldReference(fieldClass.getType(), newResolvedField.getName(),
+ newResolvedField.getType());
}
String fieldType = resolvedField.getType();
@@ -1733,41 +1753,9 @@ public class MethodAnalyzer {
targetMethod = (MethodReference)instruction.getReference();
}
- TypeProto typeProto = classPath.getClass(targetMethod.getDefiningClass());
- int methodIndex;
- try {
- methodIndex = typeProto.findMethodIndexInVtable(targetMethod);
- } catch (UnresolvedClassException ex) {
- return true;
- }
-
- if (methodIndex < 0) {
- return true;
- }
-
- Method replacementMethod = typeProto.getMethodByVtableIndex(methodIndex);
- assert replacementMethod != null;
- while (true) {
- String superType = typeProto.getSuperclass();
- if (superType == null) {
- break;
- }
- typeProto = classPath.getClass(superType);
- Method resolvedMethod = typeProto.getMethodByVtableIndex(methodIndex);
- if (resolvedMethod == null) {
- break;
- }
-
- if (!resolvedMethod.equals(replacementMethod)) {
- if (!AnalyzedMethodUtil.canAccess(typeProto, replacementMethod, true, true, true)) {
- continue;
- }
-
- replacementMethod = resolvedMethod;
- }
- }
+ MethodReference replacementMethod = normalizeMethodReference(targetMethod);
- if (replacementMethod.equals(method)) {
+ if (replacementMethod == null || replacementMethod.equals(targetMethod)) {
return true;
}
@@ -1839,7 +1827,9 @@ public class MethodAnalyzer {
// no need to check class access for invoke-super. A class can obviously access its superclass.
ClassDef thisClass = classPath.getClassDef(method.getDefiningClass());
- if (!isSuper && !TypeUtils.canAccessClass(
+ if (classPath.getClass(resolvedMethod.getDefiningClass()).isInterface()) {
+ resolvedMethod = new ReparentedMethodReference(resolvedMethod, objectRegisterTypeProto.getType());
+ } else if (!isSuper && !TypeUtils.canAccessClass(
thisClass.getType(), classPath.getClassDef(resolvedMethod.getDefiningClass()))) {
// the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different
@@ -1860,13 +1850,20 @@ public class MethodAnalyzer {
MethodReference newResolvedMethod =
classPath.getClass(methodClass.getType()).getMethodByVtableIndex(methodIndex);
if (newResolvedMethod == null) {
- // TODO: fix NPE here
throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s",
ReferenceUtil.getMethodDescriptor(resolvedMethod, true));
}
resolvedMethod = newResolvedMethod;
resolvedMethod = new ImmutableMethodReference(methodClass.getType(), resolvedMethod.getName(),
resolvedMethod.getParameterTypes(), resolvedMethod.getReturnType());
+
+ }
+
+ if (normalizeVirtualMethods) {
+ MethodReference replacementMethod = normalizeMethodReference(resolvedMethod);
+ if (replacementMethod != null) {
+ resolvedMethod = replacementMethod;
+ }
}
Instruction deodexedInstruction;
@@ -1967,4 +1964,70 @@ public class MethodAnalyzer {
"pair because it is the last register.", registerNumber));
}
}
+
+ @Nullable
+ private MethodReference normalizeMethodReference(@Nonnull MethodReference methodRef) {
+ TypeProto typeProto = classPath.getClass(methodRef.getDefiningClass());
+ int methodIndex;
+ try {
+ methodIndex = typeProto.findMethodIndexInVtable(methodRef);
+ } catch (UnresolvedClassException ex) {
+ return null;
+ }
+
+ if (methodIndex < 0) {
+ return null;
+ }
+
+ ClassProto thisClass = (ClassProto)classPath.getClass(method.getDefiningClass());
+
+ Method replacementMethod = typeProto.getMethodByVtableIndex(methodIndex);
+ assert replacementMethod != null;
+ while (true) {
+ String superType = typeProto.getSuperclass();
+ if (superType == null) {
+ break;
+ }
+ typeProto = classPath.getClass(superType);
+ Method resolvedMethod = typeProto.getMethodByVtableIndex(methodIndex);
+ if (resolvedMethod == null) {
+ break;
+ }
+
+ if (!resolvedMethod.equals(replacementMethod)) {
+ if (!AnalyzedMethodUtil.canAccess(thisClass, resolvedMethod, false, false, true)) {
+ continue;
+ }
+
+ replacementMethod = resolvedMethod;
+ }
+ }
+ return replacementMethod;
+ }
+
+ private static class ReparentedMethodReference extends BaseMethodReference {
+ private final MethodReference baseReference;
+ private final String definingClass;
+
+ public ReparentedMethodReference(MethodReference baseReference, String definingClass) {
+ this.baseReference = baseReference;
+ this.definingClass = definingClass;
+ }
+
+ @Override @Nonnull public String getName() {
+ return baseReference.getName();
+ }
+
+ @Override @Nonnull public List<? extends CharSequence> getParameterTypes() {
+ return baseReference.getParameterTypes();
+ }
+
+ @Override @Nonnull public String getReturnType() {
+ return baseReference.getReturnType();
+ }
+
+ @Nonnull @Override public String getDefiningClass() {
+ return definingClass;
+ }
+ }
} \ No newline at end of file
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/RegisterType.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/RegisterType.java
index ba782fe6..75478ca6 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/RegisterType.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/RegisterType.java
@@ -235,7 +235,7 @@ public class RegisterType {
case '[':
return getRegisterType(REFERENCE, classPath.getClass(type));
default:
- throw new ExceptionWithContext("Invalid type: " + type);
+ throw new AnalysisException("Invalid type: " + type);
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/util/ReflectionUtils.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/util/ReflectionUtils.java
index 4a4615a6..029ddb9a 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/util/ReflectionUtils.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/reflection/util/ReflectionUtils.java
@@ -31,12 +31,43 @@
package org.jf.dexlib2.analysis.reflection.util;
+import com.google.common.collect.ImmutableBiMap;
+
public class ReflectionUtils {
+
+ private static ImmutableBiMap<String, String> primitiveMap = ImmutableBiMap.<String, String>builder()
+ .put("boolean", "Z")
+ .put("int", "I")
+ .put("long", "J")
+ .put("double", "D")
+ .put("void", "V")
+ .put("float", "F")
+ .put("char", "C")
+ .put("short", "S")
+ .put("byte", "B")
+ .build();
+
public static String javaToDexName(String javaName) {
- javaName = javaName.replace('.', '/');
- if (javaName.length() > 1 && javaName.charAt(javaName.length()-1) != ';') {
- javaName = 'L' + javaName + ';';
+ if (javaName.charAt(0) == '[') {
+ return javaName.replace('.', '/');
+ }
+
+ if (primitiveMap.containsKey(javaName)) {
+ return primitiveMap.get(javaName);
+ }
+
+ return 'L' + javaName.replace('.', '/') + ';';
+ }
+
+ public static String dexToJavaName(String dexName) {
+ if (dexName.charAt(0) == '[') {
+ return dexName.replace('/', '.');
}
- return javaName;
+
+ if (primitiveMap.inverse().containsKey(dexName)) {
+ return primitiveMap.inverse().get(dexName);
+ }
+
+ return dexName.replace('/', '.').substring(1, dexName.length()-2);
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseFieldReference.java b/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseFieldReference.java
index f056f245..862e342b 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseFieldReference.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseFieldReference.java
@@ -32,6 +32,7 @@
package org.jf.dexlib2.base.reference;
import org.jf.dexlib2.iface.reference.FieldReference;
+import org.jf.dexlib2.util.ReferenceUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -64,4 +65,8 @@ public abstract class BaseFieldReference implements FieldReference {
if (res != 0) return res;
return getType().compareTo(o.getType());
}
+
+ @Override public String toString() {
+ return ReferenceUtil.getFieldDescriptor(this);
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodProtoReference.java b/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodProtoReference.java
index c0d38b0b..2fc5ed13 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodProtoReference.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodProtoReference.java
@@ -33,6 +33,7 @@ package org.jf.dexlib2.base.reference;
import com.google.common.collect.Ordering;
import org.jf.dexlib2.iface.reference.MethodProtoReference;
+import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.CharSequenceUtils;
import org.jf.util.CollectionUtils;
@@ -63,4 +64,8 @@ public abstract class BaseMethodProtoReference implements MethodProtoReference {
if (res != 0) return res;
return CollectionUtils.compareAsIterable(Ordering.usingToString(), getParameterTypes(), o.getParameterTypes());
}
+
+ @Override public String toString() {
+ return ReferenceUtil.getMethodProtoDescriptor(this);
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodReference.java b/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodReference.java
index 3ff6f7db..f297760e 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodReference.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseMethodReference.java
@@ -33,6 +33,7 @@ package org.jf.dexlib2.base.reference;
import com.google.common.collect.Ordering;
import org.jf.dexlib2.iface.reference.MethodReference;
+import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.CharSequenceUtils;
import org.jf.util.CollectionUtils;
@@ -70,4 +71,8 @@ public abstract class BaseMethodReference implements MethodReference {
if (res != 0) return res;
return CollectionUtils.compareAsIterable(Ordering.usingToString(), getParameterTypes(), o.getParameterTypes());
}
+
+ @Override public String toString() {
+ return ReferenceUtil.getMethodDescriptor(this);
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseStringReference.java b/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseStringReference.java
index c6daa91e..2f13c1ae 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseStringReference.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/base/reference/BaseStringReference.java
@@ -58,5 +58,5 @@ public abstract class BaseStringReference implements StringReference {
@Override public int length() { return getString().length(); }
@Override public char charAt(int index) { return getString().charAt(index); }
@Override public CharSequence subSequence(int start, int end) { return getString().subSequence(start, end); }
- @Override public String toString() { return getString(); }
+ @Override @Nonnull public String toString() { return getString(); }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/builder/BuilderOffsetInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/builder/BuilderOffsetInstruction.java
index d75d7b67..27e43d5e 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/builder/BuilderOffsetInstruction.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/builder/BuilderOffsetInstruction.java
@@ -33,6 +33,7 @@ package org.jf.dexlib2.builder;
import org.jf.dexlib2.Opcode;
import org.jf.dexlib2.iface.instruction.OffsetInstruction;
+import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
@@ -48,9 +49,16 @@ public abstract class BuilderOffsetInstruction extends BuilderInstruction implem
@Override public int getCodeOffset() {
int codeOffset = internalGetCodeOffset();
- if ((this.getCodeUnits() == 1 && (codeOffset < Byte.MIN_VALUE || codeOffset > Byte.MAX_VALUE)) ||
- (this.getCodeUnits() == 2 && (codeOffset < Short.MIN_VALUE || codeOffset > Short.MAX_VALUE))) {
- throw new IllegalStateException("Target is out of range");
+ if (this.getCodeUnits() == 1) {
+ if (codeOffset < Byte.MIN_VALUE || codeOffset > Byte.MAX_VALUE) {
+ throw new ExceptionWithContext("Invalid instruction offset: %d. " +
+ "Offset must be in [-128, 127]", codeOffset);
+ }
+ } else if (this.getCodeUnits() == 2) {
+ if (codeOffset < Short.MIN_VALUE || codeOffset > Short.MAX_VALUE) {
+ throw new ExceptionWithContext("Invalid instruction offset: %d. " +
+ "Offset must be in [-32768, 32767]", codeOffset);
+ }
}
return codeOffset;
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
index 32505eec..fe260c5f 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
@@ -33,16 +33,24 @@ package org.jf.dexlib2.dexbacked;
import com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.dexbacked.raw.*;
+import org.jf.dexlib2.dexbacked.reference.DexBackedFieldReference;
+import org.jf.dexlib2.dexbacked.reference.DexBackedMethodReference;
+import org.jf.dexlib2.dexbacked.reference.DexBackedStringReference;
+import org.jf.dexlib2.dexbacked.reference.DexBackedTypeReference;
import org.jf.dexlib2.dexbacked.util.FixedSizeSet;
import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.reference.Reference;
+import org.jf.dexlib2.util.DexUtil;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
+import java.util.AbstractList;
+import java.util.List;
import java.util.Set;
public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
@@ -61,13 +69,13 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
private final int classCount;
private final int classStartOffset;
- private DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
+ protected DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset, boolean verifyMagic) {
super(buf, offset);
this.opcodes = opcodes;
if (verifyMagic) {
- verifyMagicAndByteOrder(buf, offset);
+ DexUtil.verifyDexHeader(buf, offset);
}
stringCount = readSmallUint(HeaderItem.STRING_COUNT_OFFSET);
@@ -85,7 +93,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
}
public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull BaseDexBuffer buf) {
- this(opcodes, buf.buf);
+ this(opcodes, buf.buf, buf.baseOffset);
}
public DexBackedDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, int offset) {
@@ -96,22 +104,10 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
this(opcodes, buf, 0, true);
}
+ @Nonnull
public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
throws IOException {
- if (!is.markSupported()) {
- throw new IllegalArgumentException("InputStream must support mark");
- }
- is.mark(44);
- byte[] partialHeader = new byte[44];
- try {
- ByteStreams.readFully(is, partialHeader);
- } catch (EOFException ex) {
- throw new NotADexFile("File is too short");
- } finally {
- is.reset();
- }
-
- verifyMagicAndByteOrder(partialHeader, 0);
+ DexUtil.verifyDexHeader(is);
byte[] buf = ByteStreams.toByteArray(is);
return new DexBackedDexFile(opcodes, buf, 0, false);
@@ -148,25 +144,6 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
};
}
- private static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) {
- if (!HeaderItem.verifyMagic(buf, offset)) {
- StringBuilder sb = new StringBuilder("Invalid magic value:");
- for (int i=0; i<8; i++) {
- sb.append(String.format(" %02x", buf[i]));
- }
- throw new NotADexFile(sb.toString());
- }
-
- int endian = HeaderItem.getEndian(buf, offset);
- if (endian == HeaderItem.BIG_ENDIAN_TAG) {
- throw new ExceptionWithContext("Big endian dex files are not currently supported");
- }
-
- if (endian != HeaderItem.LITTLE_ENDIAN_TAG) {
- throw new ExceptionWithContext("Invalid endian tag: 0x%x", endian);
- }
- }
-
public int getStringIdItemOffset(int stringIndex) {
if (stringIndex < 0 || stringIndex >= stringCount) {
throw new InvalidItemIndex(stringIndex, "String index out of bounds: %d", stringIndex);
@@ -265,6 +242,81 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile {
return getType(typeIndex);
}
+ public List<DexBackedStringReference> getStrings() {
+ return new AbstractList<DexBackedStringReference>() {
+ @Override public DexBackedStringReference get(int index) {
+ if (index < 0 || index >= getStringCount()) {
+ throw new IndexOutOfBoundsException();
+ }
+ return new DexBackedStringReference(DexBackedDexFile.this, index);
+ }
+
+ @Override public int size() {
+ return getStringCount();
+ }
+ };
+ }
+
+ public List<DexBackedTypeReference> getTypes() {
+ return new AbstractList<DexBackedTypeReference>() {
+ @Override public DexBackedTypeReference get(int index) {
+ if (index < 0 || index >= getTypeCount()) {
+ throw new IndexOutOfBoundsException();
+ }
+ return new DexBackedTypeReference(DexBackedDexFile.this, index);
+ }
+
+ @Override public int size() {
+ return getTypeCount();
+ }
+ };
+ }
+
+ public List<DexBackedMethodReference> getMethods() {
+ return new AbstractList<DexBackedMethodReference>() {
+ @Override public DexBackedMethodReference get(int index) {
+ if (index < 0 || index >= getMethodCount()) {
+ throw new IndexOutOfBoundsException();
+ }
+ return new DexBackedMethodReference(DexBackedDexFile.this, index);
+ }
+
+ @Override public int size() {
+ return getMethodCount();
+ }
+ };
+ }
+
+ public List<DexBackedFieldReference> getFields() {
+ return new AbstractList<DexBackedFieldReference>() {
+ @Override public DexBackedFieldReference get(int index) {
+ if (index < 0 || index >= getFieldCount()) {
+ throw new IndexOutOfBoundsException();
+ }
+ return new DexBackedFieldReference(DexBackedDexFile.this, index);
+ }
+
+ @Override public int size() {
+ return getFieldCount();
+ }
+ };
+ }
+
+ public List<? extends Reference> getReferences(int referenceType) {
+ switch (referenceType) {
+ case ReferenceType.STRING:
+ return getStrings();
+ case ReferenceType.TYPE:
+ return getTypes();
+ case ReferenceType.METHOD:
+ return getMethods();
+ case ReferenceType.FIELD:
+ return getFields();
+ default:
+ throw new IllegalArgumentException(String.format("Invalid reference type: %d", referenceType));
+ }
+ }
+
@Override
@Nonnull
public DexReader readerAt(int offset) {
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedMethodImplementation.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedMethodImplementation.java
index 676d86cd..a82032a1 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedMethodImplementation.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedMethodImplementation.java
@@ -84,7 +84,7 @@ public class DexBackedMethodImplementation implements MethodImplementation {
// Does the instruction extend past the end of the method?
int offset = reader.getOffset();
if (offset > endOffset || offset < 0) {
- throw new ExceptionWithContext("The last instruction in the method is truncated");
+ throw new ExceptionWithContext("The last instruction in method %s is truncated", method);
}
return instruction;
}
@@ -129,7 +129,11 @@ public class DexBackedMethodImplementation implements MethodImplementation {
return DebugInfo.newOrEmpty(dexFile, 0, this);
}
if (debugOffset < 0) {
- System.err.println("%s: Invalid debug offset");
+ System.err.println(String.format("%s: Invalid debug offset", method));
+ return DebugInfo.newOrEmpty(dexFile, 0, this);
+ }
+ if (debugOffset >= dexFile.buf.length) {
+ System.err.println(String.format("%s: Invalid debug offset", method));
return DebugInfo.newOrEmpty(dexFile, 0, this);
}
return DebugInfo.newOrEmpty(dexFile, debugOffset, this);
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java
index 12f19db0..379ecaac 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java
@@ -35,9 +35,9 @@ import com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem;
import org.jf.dexlib2.dexbacked.util.VariableSizeList;
+import org.jf.dexlib2.util.DexUtil;
import javax.annotation.Nonnull;
-import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
@@ -49,7 +49,6 @@ public class DexBackedOdexFile extends DexBackedDexFile {
private final byte[] odexBuf;
-
public DexBackedOdexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] odexBuf, byte[] dexBuf) {
super(opcodes, dexBuf);
@@ -64,7 +63,7 @@ public class DexBackedOdexFile extends DexBackedDexFile {
return true;
}
- public List<String> getDependencies() {
+ @Nonnull public List<String> getDependencies() {
final int dexOffset = OdexHeaderItem.getDexOffset(odexBuf);
final int dependencyOffset = OdexHeaderItem.getDependenciesOffset(odexBuf) - dexOffset;
@@ -85,22 +84,9 @@ public class DexBackedOdexFile extends DexBackedDexFile {
};
}
- public static DexBackedOdexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
+ @Nonnull public static DexBackedOdexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
throws IOException {
- if (!is.markSupported()) {
- throw new IllegalArgumentException("InputStream must support mark");
- }
- is.mark(8);
- byte[] partialHeader = new byte[8];
- try {
- ByteStreams.readFully(is, partialHeader);
- } catch (EOFException ex) {
- throw new NotADexFile("File is too short");
- } finally {
- is.reset();
- }
-
- verifyMagic(partialHeader);
+ DexUtil.verifyOdexHeader(is);
is.reset();
byte[] odexBuf = new byte[OdexHeaderItem.ITEM_SIZE];
@@ -115,18 +101,8 @@ public class DexBackedOdexFile extends DexBackedDexFile {
return new DexBackedOdexFile(opcodes, odexBuf, dexBuf);
}
- private static void verifyMagic(byte[] buf) {
- if (!OdexHeaderItem.verifyMagic(buf)) {
- StringBuilder sb = new StringBuilder("Invalid magic value:");
- for (int i=0; i<8; i++) {
- sb.append(String.format(" %02x", buf[i]));
- }
- throw new NotAnOdexFile(sb.toString());
- }
- }
-
public int getOdexVersion() {
- return OdexHeaderItem.getVersion(odexBuf);
+ return OdexHeaderItem.getVersion(odexBuf, 0);
}
public static class NotAnOdexFile extends RuntimeException {
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java
index dbeb67ce..aaf942ea 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java
@@ -31,22 +31,29 @@
package org.jf.dexlib2.dexbacked;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
import com.google.common.io.ByteStreams;
import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.dexbacked.OatFile.SymbolTable.Symbol;
import org.jf.dexlib2.dexbacked.raw.HeaderItem;
+import org.jf.dexlib2.iface.MultiDexContainer;
import org.jf.util.AbstractForwardSequentialList;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.AbstractList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
-public class OatFile extends BaseDexBuffer {
+public class OatFile extends BaseDexBuffer implements MultiDexContainer<OatDexFile> {
private static final byte[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' };
private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' };
private static final int MIN_ELF_HEADER_SIZE = 52;
@@ -54,7 +61,7 @@ public class OatFile extends BaseDexBuffer {
// These are the "known working" versions that I have manually inspected the source for.
// Later version may or may not work, depending on what changed.
private static final int MIN_OAT_VERSION = 56;
- private static final int MAX_OAT_VERSION = 71;
+ private static final int MAX_OAT_VERSION = 86;
public static final int UNSUPPORTED = 0;
public static final int SUPPORTED = 1;
@@ -63,8 +70,13 @@ public class OatFile extends BaseDexBuffer {
private final boolean is64bit;
@Nonnull private final OatHeader oatHeader;
@Nonnull private final Opcodes opcodes;
+ @Nullable private final VdexProvider vdexProvider;
public OatFile(@Nonnull byte[] buf) {
+ this(buf, null);
+ }
+
+ public OatFile(@Nonnull byte[] buf, @Nullable VdexProvider vdexProvider) {
super(buf);
if (buf.length < MIN_ELF_HEADER_SIZE) {
@@ -100,6 +112,7 @@ public class OatFile extends BaseDexBuffer {
}
this.opcodes = Opcodes.forArtVersion(oatHeader.getVersion());
+ this.vdexProvider = vdexProvider;
}
private static void verifyMagic(byte[] buf) {
@@ -110,7 +123,11 @@ public class OatFile extends BaseDexBuffer {
}
}
- public static OatFile fromInputStream(@Nonnull InputStream is)
+ public static OatFile fromInputStream(@Nonnull InputStream is) throws IOException {
+ return fromInputStream(is, null);
+ }
+
+ public static OatFile fromInputStream(@Nonnull InputStream is, @Nullable VdexProvider vdexProvider)
throws IOException {
if (!is.markSupported()) {
throw new IllegalArgumentException("InputStream must support mark");
@@ -130,7 +147,7 @@ public class OatFile extends BaseDexBuffer {
is.reset();
byte[] buf = ByteStreams.toByteArray(is);
- return new OatFile(buf);
+ return new OatFile(buf, vdexProvider);
}
public int getOatVersion() {
@@ -149,6 +166,22 @@ public class OatFile extends BaseDexBuffer {
}
@Nonnull
+ public List<String> getBootClassPath() {
+ if (getOatVersion() < 75) {
+ return ImmutableList.of();
+ }
+ String bcp = oatHeader.getKeyValue("bootclasspath");
+ if (bcp == null) {
+ return ImmutableList.of();
+ }
+ return Arrays.asList(bcp.split(":"));
+ }
+
+ @Nonnull @Override public Opcodes getOpcodes() {
+ return opcodes;
+ }
+
+ @Nonnull
public List<OatDexFile> getDexFiles() {
return new AbstractForwardSequentialList<OatDexFile>() {
@Override public int size() {
@@ -156,53 +189,57 @@ public class OatFile extends BaseDexBuffer {
}
@Nonnull @Override public Iterator<OatDexFile> iterator() {
- return new Iterator<OatDexFile>() {
- int index = 0;
- int offset = oatHeader.getDexListStart();
-
- @Override public boolean hasNext() {
- return index < size();
+ return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, OatDexFile>() {
+ @Nullable @Override public OatDexFile apply(DexEntry dexEntry) {
+ return dexEntry.getDexFile();
}
+ });
+ }
+ };
+ }
- @Override public OatDexFile next() {
- int filenameLength = readSmallUint(offset);
- offset += 4;
-
- // TODO: what is the correct character encoding?
- String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
- offset += filenameLength;
-
- offset += 4; // checksum
-
- int dexOffset = readSmallUint(offset) + oatHeader.offset;
- offset += 4;
-
- int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
- offset += 4 * classCount;
-
- index++;
-
- return new OatDexFile(dexOffset, filename);
- }
+ @Nonnull @Override public List<String> getDexEntryNames() throws IOException {
+ return new AbstractForwardSequentialList<String>() {
+ @Override public int size() {
+ return oatHeader.getDexFileCount();
+ }
- @Override public void remove() {
- throw new UnsupportedOperationException();
+ @Nonnull @Override public Iterator<String> iterator() {
+ return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, String>() {
+ @Nullable @Override public String apply(DexEntry dexEntry) {
+ return dexEntry.entryName;
}
- };
+ });
}
};
}
- public class OatDexFile extends DexBackedDexFile {
+ @Nullable @Override public OatDexFile getEntry(@Nonnull String entryName) throws IOException {
+ DexEntryIterator iterator = new DexEntryIterator();
+ while (iterator.hasNext()) {
+ DexEntry entry = iterator.next();
+
+ if (entry.entryName.equals(entryName)) {
+ return entry.getDexFile();
+ }
+ }
+ return null;
+ }
+
+ public class OatDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
@Nonnull public final String filename;
- public OatDexFile(int offset, @Nonnull String filename) {
- super(opcodes, OatFile.this.buf, offset);
+ public OatDexFile(byte[] buf, int offset, @Nonnull String filename) {
+ super(opcodes, buf, offset);
this.filename = filename;
}
- public int getOatVersion() {
- return OatFile.this.getOatVersion();
+ @Nonnull @Override public String getEntryName() {
+ return filename;
+ }
+
+ @Nonnull @Override public OatFile getContainer() {
+ return OatFile.this;
}
@Override public boolean hasOdexOpcodes() {
@@ -211,57 +248,87 @@ public class OatFile extends BaseDexBuffer {
}
private class OatHeader {
- private final int offset;
+ private final int headerOffset;
public OatHeader(int offset) {
- this.offset = offset;
+ this.headerOffset = offset;
}
public boolean isValid() {
for (int i=0; i<OAT_MAGIC.length; i++) {
- if (buf[offset + i] != OAT_MAGIC[i]) {
+ if (buf[headerOffset + i] != OAT_MAGIC[i]) {
return false;
}
}
for (int i=4; i<7; i++) {
- if (buf[offset + i] < '0' || buf[offset + i] > '9') {
+ if (buf[headerOffset + i] < '0' || buf[headerOffset + i] > '9') {
return false;
}
}
- return buf[offset + 7] == 0;
+ return buf[headerOffset + 7] == 0;
}
public int getVersion() {
- return Integer.valueOf(new String(buf, offset + 4, 3));
+ return Integer.valueOf(new String(buf, headerOffset + 4, 3));
}
public int getDexFileCount() {
- return readSmallUint(offset + 20);
+ return readSmallUint(headerOffset + 20);
}
public int getKeyValueStoreSize() {
- int version = getVersion();
- if (version < 56) {
+ if (getVersion() < MIN_OAT_VERSION) {
throw new IllegalStateException("Unsupported oat version");
}
int fieldOffset = 17 * 4;
- return readSmallUint(offset + fieldOffset);
+ return readSmallUint(headerOffset + fieldOffset);
}
public int getHeaderSize() {
- int version = getVersion();
- if (version >= 56) {
- return 18*4 + getKeyValueStoreSize();
- } else {
+ if (getVersion() < MIN_OAT_VERSION) {
throw new IllegalStateException("Unsupported oat version");
}
+ return 18*4 + getKeyValueStoreSize();
+ }
+
+ @Nullable
+ public String getKeyValue(@Nonnull String key) {
+ int size = getKeyValueStoreSize();
+ int offset = headerOffset + 18 * 4;
+ int endOffset = offset + size;
+
+ while (offset < endOffset) {
+ int keyStartOffset = offset;
+ while (offset < endOffset && buf[offset] != '\0') {
+ offset++;
+ }
+ if (offset >= endOffset) {
+ throw new InvalidOatFileException("Oat file contains truncated key value store");
+ }
+ int keyEndOffset = offset;
+
+ String k = new String(buf, keyStartOffset, keyEndOffset - keyStartOffset);
+ if (k.equals(key)) {
+ int valueStartOffset = ++offset;
+ while (offset < endOffset && buf[offset] != '\0') {
+ offset++;
+ }
+ if (offset >= endOffset) {
+ throw new InvalidOatFileException("Oat file contains truncated key value store");
+ }
+ int valueEndOffset = offset;
+ return new String(buf, valueStartOffset, valueEndOffset - valueStartOffset);
+ }
+ offset++;
+ }
+ return null;
}
public int getDexListStart() {
- return offset + getHeaderSize();
+ return headerOffset + getHeaderSize();
}
}
@@ -481,7 +548,74 @@ public class OatFile extends BaseDexBuffer {
return new String(buf, start, end-start, Charset.forName("US-ASCII"));
}
+ }
+
+ private class DexEntry {
+ public final String entryName;
+ public final byte[] buf;
+ public final int dexOffset;
+
+
+ public DexEntry(String entryName, byte[] buf, int dexOffset) {
+ this.entryName = entryName;
+ this.buf = buf;
+ this.dexOffset = dexOffset;
+ }
+
+ public OatDexFile getDexFile() {
+ return new OatDexFile(buf, dexOffset, entryName);
+ }
+ }
+
+ private class DexEntryIterator implements Iterator<DexEntry> {
+ int index = 0;
+ int offset = oatHeader.getDexListStart();
+
+ @Override public boolean hasNext() {
+ return index < oatHeader.getDexFileCount();
+ }
+
+ @Override public DexEntry next() {
+ int filenameLength = readSmallUint(offset);
+ offset += 4;
+
+ // TODO: what is the correct character encoding?
+ String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII"));
+ offset += filenameLength;
+
+ offset += 4; // checksum
+
+ int dexOffset = readSmallUint(offset);
+ offset += 4;
+
+ byte[] buf;
+ if (getOatVersion() >= 87 && vdexProvider != null && vdexProvider.getVdex() != null) {
+ buf = vdexProvider.getVdex();
+ } else {
+ buf = OatFile.this.buf;
+ dexOffset += oatHeader.headerOffset;
+ }
+
+ if (getOatVersion() >= 75) {
+ offset += 4; // offset to class offsets table
+ }
+ if (getOatVersion() >= 73) {
+ offset += 4; // lookup table offset
+ }
+ if (getOatVersion() < 75) {
+ // prior to 75, the class offsets are included here directly
+ int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET);
+ offset += 4 * classCount;
+ }
+
+ index++;
+
+ return new DexEntry(filename, buf, dexOffset);
+ }
+ @Override public void remove() {
+ throw new UnsupportedOperationException();
+ }
}
public static class InvalidOatFileException extends RuntimeException {
@@ -493,4 +627,9 @@ public class OatFile extends BaseDexBuffer {
public static class NotAnOatFileException extends RuntimeException {
public NotAnOatFileException() {}
}
+
+ public interface VdexProvider {
+ @Nullable
+ byte[] getVdex();
+ }
} \ No newline at end of file
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/ZipDexContainer.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/ZipDexContainer.java
new file mode 100644
index 00000000..50052c20
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/ZipDexContainer.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.dexbacked;
+
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteStreams;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
+import org.jf.dexlib2.dexbacked.ZipDexContainer.ZipDexFile;
+import org.jf.dexlib2.iface.MultiDexContainer;
+import org.jf.dexlib2.util.DexUtil;
+import org.jf.dexlib2.util.DexUtil.InvalidFile;
+import org.jf.dexlib2.util.DexUtil.UnsupportedFile;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Represents a zip file that contains dex files (i.e. an apk or jar file)
+ */
+public class ZipDexContainer implements MultiDexContainer<ZipDexFile> {
+
+ private final File zipFilePath;
+ private final Opcodes opcodes;
+
+ /**
+ * Constructs a new ZipDexContainer for the given zip file
+ *
+ * @param zipFilePath The path to the zip file
+ * @param opcodes The Opcodes instance to use when loading dex files from this container
+ */
+ public ZipDexContainer(@Nonnull File zipFilePath, @Nonnull Opcodes opcodes) {
+ this.zipFilePath = zipFilePath;
+ this.opcodes = opcodes;
+ }
+
+ @Nonnull @Override public Opcodes getOpcodes() {
+ return opcodes;
+ }
+
+ /**
+ * Gets a list of the names of dex files in this zip file.
+ *
+ * @return A list of the names of dex files in this zip file
+ */
+ @Nonnull @Override public List<String> getDexEntryNames() throws IOException {
+ List<String> entryNames = Lists.newArrayList();
+ ZipFile zipFile = getZipFile();
+ try {
+ Enumeration<? extends ZipEntry> entriesEnumeration = zipFile.entries();
+
+ while (entriesEnumeration.hasMoreElements()) {
+ ZipEntry entry = entriesEnumeration.nextElement();
+
+ if (!isDex(zipFile, entry)) {
+ continue;
+ }
+
+ entryNames.add(entry.getName());
+ }
+
+ return entryNames;
+ } finally {
+ zipFile.close();
+ }
+ }
+
+ /**
+ * Loads a dex file from a specific named entry.
+ *
+ * @param entryName The name of the entry
+ * @return A ZipDexFile, or null if there is no entry with the given name
+ * @throws NotADexFile If the entry isn't a dex file
+ */
+ @Nullable @Override public ZipDexFile getEntry(@Nonnull String entryName) throws IOException {
+ ZipFile zipFile = getZipFile();
+ try {
+ ZipEntry entry = zipFile.getEntry(entryName);
+ if (entry == null) {
+ return null;
+ }
+
+ return loadEntry(zipFile, entry);
+ } finally {
+ zipFile.close();
+ }
+ }
+
+ public boolean isZipFile() {
+ ZipFile zipFile = null;
+ try {
+ zipFile = getZipFile();
+ return true;
+ } catch (IOException ex) {
+ return false;
+ } catch (NotAZipFileException ex) {
+ return false;
+ } finally {
+ if(zipFile != null) {
+ try {
+ zipFile.close();
+ } catch (IOException ex) {
+ // just eat it
+ }
+ }
+ }
+ }
+
+ public class ZipDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile {
+
+ private final String entryName;
+
+ protected ZipDexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf, @Nonnull String entryName) {
+ super(opcodes, buf, 0);
+ this.entryName = entryName;
+ }
+
+ @Nonnull @Override public String getEntryName() {
+ return entryName;
+ }
+
+ @Nonnull @Override public MultiDexContainer getContainer() {
+ return ZipDexContainer.this;
+ }
+ }
+
+ protected boolean isDex(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry) throws IOException {
+ InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(zipEntry));
+ try {
+ DexUtil.verifyDexHeader(inputStream);
+ } catch (NotADexFile ex) {
+ return false;
+ } catch (InvalidFile ex) {
+ return false;
+ } catch (UnsupportedFile ex) {
+ return false;
+ } finally {
+ inputStream.close();
+ }
+ return true;
+ }
+
+ protected ZipFile getZipFile() throws IOException {
+ try {
+ return new ZipFile(zipFilePath);
+ } catch (IOException ex) {
+ throw new NotAZipFileException();
+ }
+ }
+
+ @Nonnull
+ protected ZipDexFile loadEntry(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry) throws IOException {
+ InputStream inputStream = zipFile.getInputStream(zipEntry);
+ try {
+ byte[] buf = ByteStreams.toByteArray(inputStream);
+ return new ZipDexFile(opcodes, buf, zipEntry.getName());
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ public static class NotAZipFileException extends RuntimeException {
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java
index 2f3af4c3..e8db9698 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java
@@ -42,15 +42,8 @@ import javax.annotation.Nullable;
public class HeaderItem {
public static final int ITEM_SIZE = 0x70;
- /**
- * The magic numbers for dex files.
- *
- * They are: "dex\n035\0", "dex\n037\0", and "dex\n038\0".
- */
- public static final byte[][] MAGIC_VALUES= new byte[][] {
- new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00},
- new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x37, 0x00},
- new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x38, 0x00}};
+ private static final byte[] MAGIC_VALUE = new byte[] { 0x64, 0x65, 0x78, 0x0a, 0x00, 0x00, 0x00, 0x00 };
+ private static final int[] SUPPORTED_DEX_VERSIONS = new int[] { 35, 37, 38 };
public static final int LITTLE_ENDIAN_TAG = 0x12345678;
public static final int BIG_ENDIAN_TAG = 0x78563412;
@@ -231,51 +224,100 @@ public class HeaderItem {
return "Invalid";
}
-
/**
- * Get the higest magic number supported by Android for this api level.
+ * Get the highest magic number supported by Android for this api level.
* @return The dex file magic number
*/
public static byte[] getMagicForApi(int api) {
if (api < 24) {
// Prior to Android N we only support dex version 035.
- return HeaderItem.MAGIC_VALUES[0];
+ return getMagicForDexVersion(35);
} else if (api < 26) {
// On android N and later we support dex version 037.
- return HeaderItem.MAGIC_VALUES[1];
+ return getMagicForDexVersion(37);
} else {
// On android O and later we support dex version 038.
- return HeaderItem.MAGIC_VALUES[2];
+ return getMagicForDexVersion(38);
}
}
- private static int getVersion(byte[] buf, int offset) {
+ public static byte[] getMagicForDexVersion(int dexVersion) {
+ byte[] magic = MAGIC_VALUE.clone();
+
+ if (dexVersion < 0 || dexVersion > 999) {
+ throw new IllegalArgumentException("dexVersion must be within [0, 999]");
+ }
+
+ for (int i=6; i>=4; i--) {
+ int digit = dexVersion % 10;
+ magic[i] = (byte)('0' + digit);
+ dexVersion /= 10;
+ }
+
+ return magic;
+ }
+
+ /**
+ * Verifies the magic value at the beginning of a dex file
+ *
+ * @param buf A byte array containing at least the first 8 bytes of a dex file
+ * @param offset The offset within the buffer to the beginning of the dex header
+ * @return True if the magic value is valid
+ */
+ public static boolean verifyMagic(byte[] buf, int offset) {
if (buf.length - offset < 8) {
- return 0;
+ return false;
}
- boolean matches = true;
- for (int i=0; i<MAGIC_VALUES.length; i++) {
- byte[] expected = MAGIC_VALUES[i];
- matches = true;
- for (int j=0; j<8; j++) {
- if (buf[offset + j] != expected[j]) {
- matches = false;
- break;
+ for (int i=0; i<4; i++) {
+ if (buf[offset + i] != MAGIC_VALUE[i]) {
+ return false;
}
}
- if (matches) {
- return i==0?35:(i==1?37:38);
+ for (int i=4; i<7; i++) {
+ if (buf[offset + i] < '0' ||
+ buf[offset + i] > '9') {
+ return false;
}
}
- return 0;
+ if (buf[offset + 7] != MAGIC_VALUE[7]) {
+ return false;
}
- public static boolean verifyMagic(byte[] buf, int offset) {
- // verifies the magic value
- return getVersion(buf, offset) != 0;
+ return true;
+ }
+
+ /**
+ * Gets the dex version from a dex header
+ *
+ * @param buf A byte array containing at least the first 7 bytes of a dex file
+ * @param offset The offset within the buffer to the beginning of the dex header
+ * @return The dex version if the header is valid or -1 if the header is invalid
+ */
+ public static int getVersion(byte[] buf, int offset) {
+ if (!verifyMagic(buf, offset)) {
+ return -1;
+ }
+
+ return getVersionUnchecked(buf, offset);
+ }
+
+ private static int getVersionUnchecked(byte[] buf, int offset) {
+ int version = (buf[offset + 4] - '0') * 100;
+ version += (buf[offset + 5] - '0') * 10;
+ version += buf[offset + 6] - '0';
+
+ return version;
}
+ public static boolean isSupportedDexVersion(int version) {
+ for (int i=0; i<SUPPORTED_DEX_VERSIONS.length; i++) {
+ if (SUPPORTED_DEX_VERSIONS[i] == version) {
+ return true;
+ }
+ }
+ return false;
+ }
public static int getEndian(byte[] buf, int offset) {
BaseDexBuffer bdb = new BaseDexBuffer(buf);
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/OdexHeaderItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/OdexHeaderItem.java
index c6599bc4..7566834a 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/OdexHeaderItem.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/OdexHeaderItem.java
@@ -36,10 +36,8 @@ import org.jf.dexlib2.dexbacked.BaseDexBuffer;
public class OdexHeaderItem {
public static final int ITEM_SIZE = 40;
- public static final byte[][] MAGIC_VALUES= new byte[][] {
- new byte[] {0x64, 0x65, 0x79, 0x0A, 0x30, 0x33, 0x35, 0x00}, // "dey\n035\0"
- new byte[] {0x64, 0x65, 0x79, 0x0A, 0x30, 0x33, 0x36, 0x00} // "dey\n036\0"
- };
+ private static final byte[] MAGIC_VALUE = new byte[] { 0x64, 0x65, 0x79, 0x0A, 0x00, 0x00, 0x00, 0x00 };
+ private static final int[] SUPPORTED_ODEX_VERSIONS = new int[] { 35, 36 };
public static final int MAGIC_OFFSET = 0;
public static final int MAGIC_LENGTH = 8;
@@ -51,31 +49,66 @@ public class OdexHeaderItem {
public static final int AUX_LENGTH_OFFSET = 28;
public static final int FLAGS_OFFSET = 32;
- public static int getVersion(byte[] magic) {
- if (magic.length < 8) {
- return 0;
+ /**
+ * Verifies the magic value at the beginning of an odex file
+ *
+ * @param buf A byte array containing at least the first 8 bytes of an odex file
+ * @param offset The offset within the buffer to the beginning of the odex header
+ * @return True if the magic value is valid
+ */
+ public static boolean verifyMagic(byte[] buf, int offset) {
+ if (buf.length - offset < 8) {
+ return false;
}
- boolean matches = true;
- for (int i=0; i<MAGIC_VALUES.length; i++) {
- byte[] expected = MAGIC_VALUES[i];
- matches = true;
- for (int j=0; j<8; j++) {
- if (magic[j] != expected[j]) {
- matches = false;
- break;
- }
+ for (int i=0; i<4; i++) {
+ if (buf[offset + i] != MAGIC_VALUE[i]) {
+ return false;
}
- if (matches) {
- return i==0?35:36;
+ }
+ for (int i=4; i<7; i++) {
+ if (buf[offset + i] < '0' ||
+ buf[offset + i] > '9') {
+ return false;
}
}
- return 0;
+ if (buf[offset + 7] != MAGIC_VALUE[7]) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the dex version from an odex header
+ *
+ * @param buf A byte array containing at least the first 7 bytes of an odex file
+ * @param offset The offset within the buffer to the beginning of the odex header
+ * @return The odex version if the header is valid or -1 if the header is invalid
+ */
+ public static int getVersion(byte[] buf, int offset) {
+ if (!verifyMagic(buf, offset)) {
+ return -1;
+ }
+
+ return getVersionUnchecked(buf, offset);
+ }
+
+ private static int getVersionUnchecked(byte[] buf, int offset) {
+ int version = (buf[offset + 4] - '0') * 100;
+ version += (buf[offset + 5] - '0') * 10;
+ version += buf[offset + 6] - '0';
+
+ return version;
}
- public static boolean verifyMagic(byte[] buf) {
- // verifies the magic value
- return getVersion(buf) != 0;
+ public static boolean isSupportedOdexVersion(int version) {
+ for (int i=0; i<SUPPORTED_ODEX_VERSIONS.length; i++) {
+ if (SUPPORTED_ODEX_VERSIONS[i] == version) {
+ return true;
+ }
+ }
+ return false;
}
public static int getDexOffset(byte[] buf) {
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/MultiDexContainer.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/MultiDexContainer.java
new file mode 100644
index 00000000..6c4e769a
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/MultiDexContainer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.iface;
+
+import org.jf.dexlib2.Opcodes;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This class represents a dex container that can contain multiple, named dex files
+ */
+public interface MultiDexContainer<T extends DexFile> {
+ /**
+ * @return A list of the names of dex entries in this container
+ */
+ @Nonnull List<String> getDexEntryNames() throws IOException;
+
+ /**
+ * Gets the dex entry with the given name
+ *
+ * @param entryName The name of the entry
+ * @return A DexFile, or null if no entry with that name is found
+ */
+ @Nullable T getEntry(@Nonnull String entryName) throws IOException;
+
+ /**
+ * @return the Opcodes instance associated with this MultiDexContainer
+ */
+ @Nonnull Opcodes getOpcodes();
+
+ /**
+ * This class represents a dex file that is contained in a MultiDexContainer
+ */
+ interface MultiDexFile extends DexFile {
+ /**
+ * @return The name of this entry within its container
+ */
+ @Nonnull String getEntryName();
+
+ /**
+ * @return The MultiDexContainer that contains this dex file
+ */
+ @Nonnull MultiDexContainer<? extends MultiDexFile> getContainer();
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/immutable/ImmutableDexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/immutable/ImmutableDexFile.java
index 2112bd07..76f39a14 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/immutable/ImmutableDexFile.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/immutable/ImmutableDexFile.java
@@ -45,18 +45,6 @@ public class ImmutableDexFile implements DexFile {
@Nonnull protected final ImmutableSet<? extends ImmutableClassDef> classes;
@Nonnull private final Opcodes opcodes;
- @Deprecated
- public ImmutableDexFile(@Nullable Collection<? extends ClassDef> classes) {
- this.classes = ImmutableClassDef.immutableSetOf(classes);
- this.opcodes = Opcodes.forApi(19);
- }
-
- @Deprecated
- public ImmutableDexFile(@Nullable ImmutableSet<? extends ImmutableClassDef> classes) {
- this.classes = ImmutableUtils.nullToEmptySet(classes);
- this.opcodes = Opcodes.forApi(19);
- }
-
public ImmutableDexFile(@Nonnull Opcodes opcodes, @Nullable Collection<? extends ClassDef> classes) {
this.classes = ImmutableClassDef.immutableSetOf(classes);
this.opcodes = opcodes;
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/DexUtil.java b/dexlib2/src/main/java/org/jf/dexlib2/util/DexUtil.java
new file mode 100644
index 00000000..389edfd7
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/util/DexUtil.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.util;
+
+import com.google.common.io.ByteStreams;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
+import org.jf.dexlib2.dexbacked.DexBackedOdexFile.NotAnOdexFile;
+import org.jf.dexlib2.dexbacked.raw.HeaderItem;
+import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem;
+
+import javax.annotation.Nonnull;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class DexUtil {
+
+ /**
+ * Reads in the dex header from the given input stream and verifies that it is valid and a supported version
+ *
+ * The inputStream must support mark(), and will be reset to initial position upon exiting the method
+ *
+ * @param inputStream An input stream that is positioned at a dex header
+ * @throws NotADexFile If the file is not a dex file
+ * @throws InvalidFile If the header appears to be a dex file, but is not valid for some reason
+ * @throws UnsupportedFile If the dex header is valid, but uses unsupported functionality
+ */
+ public static void verifyDexHeader(@Nonnull InputStream inputStream) throws IOException {
+ if (!inputStream.markSupported()) {
+ throw new IllegalArgumentException("InputStream must support mark");
+ }
+ inputStream.mark(44);
+ byte[] partialHeader = new byte[44];
+ try {
+ ByteStreams.readFully(inputStream, partialHeader);
+ } catch (EOFException ex) {
+ throw new NotADexFile("File is too short");
+ } finally {
+ inputStream.reset();
+ }
+
+ verifyDexHeader(partialHeader, 0);
+ }
+
+ /**
+ * Verifies that the dex header is valid and a supported version
+ *
+ * @param buf A byte array containing at least the first 44 bytes of a dex file
+ * @param offset The offset within the array to the dex header
+ * @throws NotADexFile If the file is not a dex file
+ * @throws InvalidFile If the header appears to be a dex file, but is not valid for some reason
+ * @throws UnsupportedFile If the dex header is valid, but uses unsupported functionality
+ */
+ public static void verifyDexHeader(@Nonnull byte[] buf, int offset) {
+ int dexVersion = HeaderItem.getVersion(buf, offset);
+ if (dexVersion == -1) {
+ StringBuilder sb = new StringBuilder("Not a valid dex magic value:");
+ for (int i=0; i<8; i++) {
+ sb.append(String.format(" %02x", buf[i]));
+ }
+ throw new NotADexFile(sb.toString());
+ }
+
+ if (!HeaderItem.isSupportedDexVersion(dexVersion)) {
+ throw new UnsupportedFile(String.format("Dex version %03d is not supported", dexVersion));
+ }
+
+ int endian = HeaderItem.getEndian(buf, offset);
+ if (endian == HeaderItem.BIG_ENDIAN_TAG) {
+ throw new UnsupportedFile("Big endian dex files are not supported");
+ }
+
+ if (endian != HeaderItem.LITTLE_ENDIAN_TAG) {
+ throw new InvalidFile(String.format("Invalid endian tag: 0x%x", endian));
+ }
+ }
+
+ /**
+ * Reads in the odex header from the given input stream and verifies that it is valid and a supported version
+ *
+ * The inputStream must support mark(), and will be reset to initial position upon exiting the method
+ *
+ * @param inputStream An input stream that is positioned at an odex header
+ * @throws NotAnOdexFile If the file is not an odex file
+ * @throws UnsupportedFile If the odex header is valid, but is an unsupported version
+ */
+ public static void verifyOdexHeader(@Nonnull InputStream inputStream) throws IOException {
+ if (!inputStream.markSupported()) {
+ throw new IllegalArgumentException("InputStream must support mark");
+ }
+ inputStream.mark(8);
+ byte[] partialHeader = new byte[8];
+ try {
+ ByteStreams.readFully(inputStream, partialHeader);
+ } catch (EOFException ex) {
+ throw new NotAnOdexFile("File is too short");
+ } finally {
+ inputStream.reset();
+ }
+
+ verifyOdexHeader(partialHeader, 0);
+ }
+
+ /**
+ * Verifies that the odex header is valid and a supported version
+ *
+ * @param buf A byte array containing at least the first 8 bytes of an odex file
+ * @param offset The offset within the array to the odex header
+ * @throws NotAnOdexFile If the file is not an odex file
+ * @throws UnsupportedFile If the odex header is valid, but uses unsupported functionality
+ */
+ public static void verifyOdexHeader(@Nonnull byte[] buf, int offset) {
+ int odexVersion = OdexHeaderItem.getVersion(buf, offset);
+ if (odexVersion == -1) {
+ StringBuilder sb = new StringBuilder("Not a valid odex magic value:");
+ for (int i=0; i<8; i++) {
+ sb.append(String.format(" %02x", buf[i]));
+ }
+ throw new NotAnOdexFile(sb.toString());
+ }
+
+ if (!OdexHeaderItem.isSupportedOdexVersion(odexVersion)) {
+ throw new UnsupportedFile(String.format("Odex version %03d is not supported", odexVersion));
+ }
+ }
+
+ public static class InvalidFile extends RuntimeException {
+ public InvalidFile() {
+ }
+
+ public InvalidFile(String message) {
+ super(message);
+ }
+
+ public InvalidFile(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public InvalidFile(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ public static class UnsupportedFile extends RuntimeException {
+ public UnsupportedFile() {
+ }
+
+ public UnsupportedFile(String message) {
+ super(message);
+ }
+
+ public UnsupportedFile(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UnsupportedFile(Throwable cause) {
+ super(cause);
+ }
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java
index 6ca1ce93..00cce65d 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java
@@ -53,11 +53,7 @@ import org.jf.dexlib2.iface.instruction.Instruction;
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction;
import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
import org.jf.dexlib2.iface.instruction.formats.*;
-import org.jf.dexlib2.iface.reference.FieldReference;
-import org.jf.dexlib2.iface.reference.MethodProtoReference;
-import org.jf.dexlib2.iface.reference.MethodReference;
-import org.jf.dexlib2.iface.reference.StringReference;
-import org.jf.dexlib2.iface.reference.TypeReference;
+import org.jf.dexlib2.iface.reference.*;
import org.jf.dexlib2.util.InstructionUtil;
import org.jf.dexlib2.util.MethodUtil;
import org.jf.dexlib2.util.ReferenceUtil;
@@ -92,7 +88,18 @@ public abstract class DexWriter<
TypeListKey,
FieldKey, MethodKey,
EncodedValue,
- AnnotationElement extends org.jf.dexlib2.iface.AnnotationElement> {
+ AnnotationElement extends org.jf.dexlib2.iface.AnnotationElement,
+ StringSectionType extends StringSection<StringKey, StringRef>,
+ TypeSectionType extends TypeSection<StringKey, TypeKey, TypeRef>,
+ ProtoSectionType extends ProtoSection<StringKey, TypeKey, ProtoRefKey, TypeListKey>,
+ FieldSectionType extends FieldSection<StringKey, TypeKey, FieldRefKey, FieldKey>,
+ MethodSectionType extends MethodSection<StringKey, TypeKey, ProtoRefKey, MethodRefKey, MethodKey>,
+ ClassSectionType extends ClassSection<StringKey, TypeKey, TypeListKey, ClassKey, FieldKey, MethodKey,
+ AnnotationSetKey, EncodedValue>,
+ TypeListSectionType extends TypeListSection<TypeKey, TypeListKey>,
+ AnnotationSectionType extends AnnotationSection<StringKey, TypeKey, AnnotationKey, AnnotationElement,
+ EncodedValue>,
+ AnnotationSetSectionType extends AnnotationSetSection<AnnotationKey, AnnotationSetKey>> {
public static final int NO_INDEX = -1;
public static final int NO_OFFSET = 0;
@@ -124,43 +131,34 @@ public abstract class DexWriter<
protected int numCodeItemItems = 0;
protected int numClassDataItems = 0;
- protected final StringSection<StringKey, StringRef> stringSection;
- protected final TypeSection<StringKey, TypeKey, TypeRef> typeSection;
- protected final ProtoSection<StringKey, TypeKey, ProtoRefKey, TypeListKey> protoSection;
- protected final FieldSection<StringKey, TypeKey, FieldRefKey, FieldKey> fieldSection;
- protected final MethodSection<StringKey, TypeKey, ProtoRefKey, MethodRefKey, MethodKey> methodSection;
- protected final ClassSection<StringKey, TypeKey, TypeListKey, ClassKey, FieldKey, MethodKey, AnnotationSetKey,
- EncodedValue> classSection;
+ public final StringSectionType stringSection;
+ public final TypeSectionType typeSection;
+ public final ProtoSectionType protoSection;
+ public final FieldSectionType fieldSection;
+ public final MethodSectionType methodSection;
+ public final ClassSectionType classSection;
- protected final TypeListSection<TypeKey, TypeListKey> typeListSection;
- protected final AnnotationSection<StringKey, TypeKey, AnnotationKey, AnnotationElement, EncodedValue> annotationSection;
- protected final AnnotationSetSection<AnnotationKey, AnnotationSetKey> annotationSetSection;
-
- protected DexWriter(Opcodes opcodes,
- StringSection<StringKey, StringRef> stringSection,
- TypeSection<StringKey, TypeKey, TypeRef> typeSection,
- ProtoSection<StringKey, TypeKey, ProtoRefKey, TypeListKey> protoSection,
- FieldSection<StringKey, TypeKey, FieldRefKey, FieldKey> fieldSection,
- MethodSection<StringKey, TypeKey, ProtoRefKey, MethodRefKey, MethodKey> methodSection,
- ClassSection<StringKey, TypeKey, TypeListKey, ClassKey, FieldKey, MethodKey, AnnotationSetKey,
- EncodedValue> classSection,
- TypeListSection<TypeKey, TypeListKey> typeListSection,
- AnnotationSection<StringKey, TypeKey, AnnotationKey, AnnotationElement,
- EncodedValue> annotationSection,
- AnnotationSetSection<AnnotationKey, AnnotationSetKey> annotationSetSection) {
+ public final TypeListSectionType typeListSection;
+ public final AnnotationSectionType annotationSection;
+ public final AnnotationSetSectionType annotationSetSection;
+
+ protected DexWriter(Opcodes opcodes) {
this.opcodes = opcodes;
- this.stringSection = stringSection;
- this.typeSection = typeSection;
- this.protoSection = protoSection;
- this.fieldSection = fieldSection;
- this.methodSection = methodSection;
- this.classSection = classSection;
- this.typeListSection = typeListSection;
- this.annotationSection = annotationSection;
- this.annotationSetSection = annotationSetSection;
+ SectionProvider sectionProvider = getSectionProvider();
+ this.stringSection = sectionProvider.getStringSection();
+ this.typeSection = sectionProvider.getTypeSection();
+ this.protoSection = sectionProvider.getProtoSection();
+ this.fieldSection = sectionProvider.getFieldSection();
+ this.methodSection = sectionProvider.getMethodSection();
+ this.classSection = sectionProvider.getClassSection();
+ this.typeListSection = sectionProvider.getTypeListSection();
+ this.annotationSection = sectionProvider.getAnnotationSection();
+ this.annotationSetSection = sectionProvider.getAnnotationSetSection();
}
+ @Nonnull protected abstract SectionProvider getSectionProvider();
+
protected abstract void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,
@Nonnull EncodedValue encodedValue) throws IOException;
@@ -192,12 +190,12 @@ public abstract class DexWriter<
private int getDataSectionOffset() {
return HeaderItem.ITEM_SIZE +
- stringSection.getItems().size() * StringIdItem.ITEM_SIZE +
- typeSection.getItems().size() * TypeIdItem.ITEM_SIZE +
- protoSection.getItems().size() * ProtoIdItem.ITEM_SIZE +
- fieldSection.getItems().size() * FieldIdItem.ITEM_SIZE +
- methodSection.getItems().size() * MethodIdItem.ITEM_SIZE +
- classSection.getItems().size() * ClassDefItem.ITEM_SIZE;
+ stringSection.getItemCount() * StringIdItem.ITEM_SIZE +
+ typeSection.getItemCount() * TypeIdItem.ITEM_SIZE +
+ protoSection.getItemCount() * ProtoIdItem.ITEM_SIZE +
+ fieldSection.getItemCount() * FieldIdItem.ITEM_SIZE +
+ methodSection.getItemCount() * MethodIdItem.ITEM_SIZE +
+ classSection.getItemCount() * ClassDefItem.ITEM_SIZE;
}
@Nonnull
@@ -227,6 +225,22 @@ public abstract class DexWriter<
return classReferences;
}
+ /**
+ * Checks whether any of the size-sensitive constant pools have overflowed.
+ *
+ * This checks whether the type, method, field pools are larger than 64k entries.
+ *
+ * Note that even if this returns true, it may still be possible to successfully write the dex file, if the
+ * overflowed items are not referenced anywhere that uses a 16-bit index
+ *
+ * @return true if any of the size-sensitive constant pools have overflowed
+ */
+ public boolean hasOverflowed() {
+ return methodSection.getItemCount() > (1 << 16) ||
+ typeSection.getItemCount() > (1 << 16) ||
+ fieldSection.getItemCount() > (1 << 16);
+ }
+
public void writeTo(@Nonnull DexDataStore dest) throws IOException {
this.writeTo(dest, MemoryDeferredOutputStream.getFactory());
}
@@ -801,7 +815,14 @@ public abstract class DexWriter<
int debugItemOffset = writeDebugItem(offsetWriter, debugWriter,
classSection.getParameterNames(methodKey), debugItems);
- int codeItemOffset = writeCodeItem(codeWriter, ehBuf, methodKey, tryBlocks, instructions, debugItemOffset);
+ int codeItemOffset;
+ try {
+ codeItemOffset = writeCodeItem(
+ codeWriter, ehBuf, methodKey, tryBlocks, instructions, debugItemOffset);
+ } catch (RuntimeException ex) {
+ throw new ExceptionWithContext(ex, "Exception occurred while writing code_item for method %s",
+ methodSection.getMethodReference(methodKey));
+ }
if (codeItemOffset != -1) {
codeOffsets.add(new CodeItemOffset<MethodKey>(methodKey, codeItemOffset));
@@ -950,105 +971,111 @@ public abstract class DexWriter<
methodSection, protoSection);
writer.writeInt(codeUnitCount);
+ int codeOffset = 0;
for (Instruction instruction: instructions) {
- switch (instruction.getOpcode().format) {
- case Format10t:
- instructionWriter.write((Instruction10t)instruction);
- break;
- case Format10x:
- instructionWriter.write((Instruction10x)instruction);
- break;
- case Format11n:
- instructionWriter.write((Instruction11n)instruction);
- break;
- case Format11x:
- instructionWriter.write((Instruction11x)instruction);
- break;
- case Format12x:
- instructionWriter.write((Instruction12x)instruction);
- break;
- case Format20bc:
- instructionWriter.write((Instruction20bc)instruction);
- break;
- case Format20t:
- instructionWriter.write((Instruction20t)instruction);
- break;
- case Format21c:
- instructionWriter.write((Instruction21c)instruction);
- break;
- case Format21ih:
- instructionWriter.write((Instruction21ih)instruction);
- break;
- case Format21lh:
- instructionWriter.write((Instruction21lh)instruction);
- break;
- case Format21s:
- instructionWriter.write((Instruction21s)instruction);
- break;
- case Format21t:
- instructionWriter.write((Instruction21t)instruction);
- break;
- case Format22b:
- instructionWriter.write((Instruction22b)instruction);
- break;
- case Format22c:
- instructionWriter.write((Instruction22c)instruction);
- break;
- case Format22s:
- instructionWriter.write((Instruction22s)instruction);
- break;
- case Format22t:
- instructionWriter.write((Instruction22t)instruction);
- break;
- case Format22x:
- instructionWriter.write((Instruction22x)instruction);
- break;
- case Format23x:
- instructionWriter.write((Instruction23x)instruction);
- break;
- case Format30t:
- instructionWriter.write((Instruction30t)instruction);
- break;
- case Format31c:
- instructionWriter.write((Instruction31c)instruction);
- break;
- case Format31i:
- instructionWriter.write((Instruction31i)instruction);
- break;
- case Format31t:
- instructionWriter.write((Instruction31t)instruction);
- break;
- case Format32x:
- instructionWriter.write((Instruction32x)instruction);
- break;
- case Format35c:
- instructionWriter.write((Instruction35c)instruction);
- break;
- case Format3rc:
- instructionWriter.write((Instruction3rc)instruction);
- break;
- case Format45cc:
- instructionWriter.write((Instruction45cc) instruction);
- break;
- case Format4rcc:
- instructionWriter.write((Instruction4rcc) instruction);
- break;
- case Format51l:
- instructionWriter.write((Instruction51l)instruction);
- break;
- case ArrayPayload:
- instructionWriter.write((ArrayPayload)instruction);
- break;
- case PackedSwitchPayload:
- instructionWriter.write((PackedSwitchPayload)instruction);
- break;
- case SparseSwitchPayload:
- instructionWriter.write((SparseSwitchPayload)instruction);
- break;
- default:
- throw new ExceptionWithContext("Unsupported instruction format: %s",
- instruction.getOpcode().format);
+ try {
+ switch (instruction.getOpcode().format) {
+ case Format10t:
+ instructionWriter.write((Instruction10t)instruction);
+ break;
+ case Format10x:
+ instructionWriter.write((Instruction10x)instruction);
+ break;
+ case Format11n:
+ instructionWriter.write((Instruction11n)instruction);
+ break;
+ case Format11x:
+ instructionWriter.write((Instruction11x)instruction);
+ break;
+ case Format12x:
+ instructionWriter.write((Instruction12x)instruction);
+ break;
+ case Format20bc:
+ instructionWriter.write((Instruction20bc)instruction);
+ break;
+ case Format20t:
+ instructionWriter.write((Instruction20t)instruction);
+ break;
+ case Format21c:
+ instructionWriter.write((Instruction21c)instruction);
+ break;
+ case Format21ih:
+ instructionWriter.write((Instruction21ih)instruction);
+ break;
+ case Format21lh:
+ instructionWriter.write((Instruction21lh)instruction);
+ break;
+ case Format21s:
+ instructionWriter.write((Instruction21s)instruction);
+ break;
+ case Format21t:
+ instructionWriter.write((Instruction21t)instruction);
+ break;
+ case Format22b:
+ instructionWriter.write((Instruction22b)instruction);
+ break;
+ case Format22c:
+ instructionWriter.write((Instruction22c)instruction);
+ break;
+ case Format22s:
+ instructionWriter.write((Instruction22s)instruction);
+ break;
+ case Format22t:
+ instructionWriter.write((Instruction22t)instruction);
+ break;
+ case Format22x:
+ instructionWriter.write((Instruction22x)instruction);
+ break;
+ case Format23x:
+ instructionWriter.write((Instruction23x)instruction);
+ break;
+ case Format30t:
+ instructionWriter.write((Instruction30t)instruction);
+ break;
+ case Format31c:
+ instructionWriter.write((Instruction31c)instruction);
+ break;
+ case Format31i:
+ instructionWriter.write((Instruction31i)instruction);
+ break;
+ case Format31t:
+ instructionWriter.write((Instruction31t)instruction);
+ break;
+ case Format32x:
+ instructionWriter.write((Instruction32x)instruction);
+ break;
+ case Format35c:
+ instructionWriter.write((Instruction35c)instruction);
+ break;
+ case Format3rc:
+ instructionWriter.write((Instruction3rc)instruction);
+ break;
+ case Format45cc:
+ instructionWriter.write((Instruction45cc)instruction);
+ break;
+ case Format4rcc:
+ instructionWriter.write((Instruction4rcc)instruction);
+ break;
+ case Format51l:
+ instructionWriter.write((Instruction51l)instruction);
+ break;
+ case ArrayPayload:
+ instructionWriter.write((ArrayPayload)instruction);
+ break;
+ case PackedSwitchPayload:
+ instructionWriter.write((PackedSwitchPayload)instruction);
+ break;
+ case SparseSwitchPayload:
+ instructionWriter.write((SparseSwitchPayload)instruction);
+ break;
+ default:
+ throw new ExceptionWithContext("Unsupported instruction format: %s",
+ instruction.getOpcode().format);
+ }
+ } catch (RuntimeException ex) {
+ throw new ExceptionWithContext(ex, "Error while writing instruction at code offset 0x%x", codeOffset);
}
+ codeOffset += instruction.getCodeUnits();
}
if (tryBlocks.size() > 0) {
@@ -1274,4 +1301,16 @@ public abstract class DexWriter<
// (https://code.google.com/p/android/issues/detail?id=35304)
return (opcodes.api < 17);
}
+
+ public abstract class SectionProvider {
+ @Nonnull public abstract StringSectionType getStringSection();
+ @Nonnull public abstract TypeSectionType getTypeSection();
+ @Nonnull public abstract ProtoSectionType getProtoSection();
+ @Nonnull public abstract FieldSectionType getFieldSection();
+ @Nonnull public abstract MethodSectionType getMethodSection();
+ @Nonnull public abstract ClassSectionType getClassSection();
+ @Nonnull public abstract TypeListSectionType getTypeListSection();
+ @Nonnull public abstract AnnotationSectionType getAnnotationSection();
+ @Nonnull public abstract AnnotationSetSectionType getAnnotationSetSection();
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/IndexSection.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/IndexSection.java
index 53d14474..8abc776b 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/IndexSection.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/IndexSection.java
@@ -38,4 +38,5 @@ import java.util.Map;
public interface IndexSection<Key> {
int getItemIndex(@Nonnull Key key);
@Nonnull Collection<? extends Map.Entry<? extends Key, Integer>> getItems();
+ int getItemCount();
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/MethodSection.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/MethodSection.java
index 32e6d6bf..f31e84c8 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/MethodSection.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/MethodSection.java
@@ -39,6 +39,7 @@ import javax.annotation.Nonnull;
public interface MethodSection<StringKey, TypeKey, ProtoRefKey extends MethodProtoReference,
MethodRefKey extends MethodReference, MethodKey>
extends IndexSection<MethodRefKey> {
+ @Nonnull MethodRefKey getMethodReference(@Nonnull MethodKey key);
@Nonnull TypeKey getDefiningClass(@Nonnull MethodRefKey key);
@Nonnull ProtoRefKey getPrototype(@Nonnull MethodRefKey key);
@Nonnull ProtoRefKey getPrototype(@Nonnull MethodKey key);
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BaseBuilderPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BaseBuilderPool.java
new file mode 100644
index 00000000..64ba1d7b
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BaseBuilderPool.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.writer.builder;
+
+import javax.annotation.Nonnull;
+
+public class BaseBuilderPool {
+ @Nonnull protected final DexBuilder dexBuilder;
+
+ public BaseBuilderPool(@Nonnull DexBuilder dexBuilder) {
+ this.dexBuilder = dexBuilder;
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationPool.java
index e16bff0c..37e536cd 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationPool.java
@@ -41,14 +41,13 @@ import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
-class BuilderAnnotationPool implements AnnotationSection<BuilderStringReference, BuilderTypeReference,
- BuilderAnnotation, BuilderAnnotationElement, BuilderEncodedValue> {
- @Nonnull private final BuilderContext context;
+class BuilderAnnotationPool extends BaseBuilderPool implements AnnotationSection<BuilderStringReference,
+ BuilderTypeReference, BuilderAnnotation, BuilderAnnotationElement, BuilderEncodedValue> {
@Nonnull private final ConcurrentMap<Annotation, BuilderAnnotation> internedItems =
Maps.newConcurrentMap();
- BuilderAnnotationPool(@Nonnull BuilderContext context) {
- this.context = context;
+ public BuilderAnnotationPool(@Nonnull DexBuilder dexBuilder) {
+ super(dexBuilder);
}
@Nonnull public BuilderAnnotation internAnnotation(@Nonnull Annotation annotation) {
@@ -59,8 +58,8 @@ class BuilderAnnotationPool implements AnnotationSection<BuilderStringReference,
BuilderAnnotation dexBuilderAnnotation = new BuilderAnnotation(
annotation.getVisibility(),
- context.typePool.internType(annotation.getType()),
- context.internAnnotationElements(annotation.getElements()));
+ dexBuilder.typeSection.internType(annotation.getType()),
+ dexBuilder.internAnnotationElements(annotation.getElements()));
ret = internedItems.putIfAbsent(dexBuilderAnnotation, dexBuilderAnnotation);
return ret==null?dexBuilderAnnotation:ret;
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSet.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSet.java
index 43ca7452..ef9a9d63 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSet.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSet.java
@@ -39,7 +39,7 @@ import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Set;
-class BuilderAnnotationSet extends AbstractSet<BuilderAnnotation> {
+public class BuilderAnnotationSet extends AbstractSet<BuilderAnnotation> {
public static final BuilderAnnotationSet EMPTY =
new BuilderAnnotationSet(ImmutableSet.<BuilderAnnotation>of());
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSetPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSetPool.java
index 353190fb..45af5cf3 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSetPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderAnnotationSetPool.java
@@ -46,13 +46,13 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
-class BuilderAnnotationSetPool implements AnnotationSetSection<BuilderAnnotation, BuilderAnnotationSet> {
- @Nonnull private final BuilderContext context;
+class BuilderAnnotationSetPool extends BaseBuilderPool
+ implements AnnotationSetSection<BuilderAnnotation, BuilderAnnotationSet> {
@Nonnull private final ConcurrentMap<Set<? extends Annotation>, BuilderAnnotationSet> internedItems =
Maps.newConcurrentMap();
- BuilderAnnotationSetPool(@Nonnull BuilderContext context) {
- this.context = context;
+ public BuilderAnnotationSetPool(@Nonnull DexBuilder dexBuilder) {
+ super(dexBuilder);
}
@Nonnull public BuilderAnnotationSet internAnnotationSet(@Nullable Set<? extends Annotation> annotations) {
@@ -69,7 +69,7 @@ class BuilderAnnotationSetPool implements AnnotationSetSection<BuilderAnnotation
ImmutableSet.copyOf(Iterators.transform(annotations.iterator(),
new Function<Annotation, BuilderAnnotation>() {
@Nullable @Override public BuilderAnnotation apply(Annotation input) {
- return context.annotationPool.internAnnotation(input);
+ return dexBuilder.annotationSection.internAnnotation(input);
}
})));
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassDef.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassDef.java
index 10215925..9938a6e0 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassDef.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassDef.java
@@ -31,7 +31,6 @@
package org.jf.dexlib2.writer.builder;
-import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.*;
import org.jf.dexlib2.base.reference.BaseTypeReference;
@@ -81,14 +80,10 @@ public class BuilderClassDef extends BaseTypeReference implements ClassDef {
this.interfaces = interfaces;
this.sourceFile = sourceFile;
this.annotations = annotations;
- this.staticFields = ImmutableSortedSet.copyOf(
- (Iterable<? extends BuilderField>)Iterables.filter(fields, FieldUtil.FIELD_IS_STATIC));
- this.instanceFields = ImmutableSortedSet.copyOf(
- (Iterable<? extends BuilderField>)Iterables.filter(fields, FieldUtil.FIELD_IS_INSTANCE));
- this.directMethods = ImmutableSortedSet.copyOf(
- (Iterable<? extends BuilderMethod>)Iterables.filter(methods, MethodUtil.METHOD_IS_DIRECT));
- this.virtualMethods = ImmutableSortedSet.copyOf(
- (Iterable<? extends BuilderMethod>)Iterables.filter(methods, MethodUtil.METHOD_IS_VIRTUAL));
+ this.staticFields = ImmutableSortedSet.copyOf(Iterables.filter(fields, FieldUtil.FIELD_IS_STATIC));
+ this.instanceFields = ImmutableSortedSet.copyOf(Iterables.filter(fields, FieldUtil.FIELD_IS_INSTANCE));
+ this.directMethods = ImmutableSortedSet.copyOf(Iterables.filter(methods, MethodUtil.METHOD_IS_DIRECT));
+ this.virtualMethods = ImmutableSortedSet.copyOf(Iterables.filter(methods, MethodUtil.METHOD_IS_VIRTUAL));
}
@Nonnull @Override public String getType() { return type.getType(); }
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java
index 29980f32..232b4824 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java
@@ -60,12 +60,14 @@ import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
-public class BuilderClassPool implements ClassSection<BuilderStringReference, BuilderTypeReference, BuilderTypeList,
- BuilderClassDef, BuilderField, BuilderMethod, BuilderAnnotationSet, BuilderEncodedValue> {
+public class BuilderClassPool extends BaseBuilderPool implements ClassSection<BuilderStringReference,
+ BuilderTypeReference, BuilderTypeList, BuilderClassDef, BuilderField, BuilderMethod, BuilderAnnotationSet,
+ BuilderEncodedValue> {
@Nonnull private final ConcurrentMap<String, BuilderClassDef> internedItems =
Maps.newConcurrentMap();
- BuilderClassPool() {
+ public BuilderClassPool(@Nonnull DexBuilder dexBuilder) {
+ super(dexBuilder);
}
@Nonnull BuilderClassDef internClass(@Nonnull BuilderClassDef classDef) {
@@ -441,4 +443,8 @@ public class BuilderClassPool implements ClassSection<BuilderStringReference, Bu
}
};
}
+
+ @Override public int getItemCount() {
+ return internedItems.size();
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderContext.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderContext.java
deleted file mode 100644
index e6f8e225..00000000
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderContext.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2013, Google Inc.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.jf.dexlib2.writer.builder;
-
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterators;
-import org.jf.dexlib2.ValueType;
-import org.jf.dexlib2.iface.AnnotationElement;
-import org.jf.dexlib2.iface.value.*;
-import org.jf.dexlib2.writer.builder.BuilderEncodedValues.*;
-import org.jf.util.ExceptionWithContext;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.util.Set;
-
-class BuilderContext {
- // keep our own local references to the various pools, using the Builder specific pool type;
- @Nonnull final BuilderStringPool stringPool;
- @Nonnull final BuilderTypePool typePool;
- @Nonnull final BuilderFieldPool fieldPool;
- @Nonnull final BuilderMethodPool methodPool;
- @Nonnull final BuilderProtoPool protoPool;
- @Nonnull final BuilderClassPool classPool;
-
- @Nonnull final BuilderTypeListPool typeListPool;
- @Nonnull final BuilderAnnotationPool annotationPool;
- @Nonnull final BuilderAnnotationSetPool annotationSetPool;
-
-
- BuilderContext() {
- this.stringPool = new BuilderStringPool();
- this.typePool = new BuilderTypePool(this);
- this.fieldPool = new BuilderFieldPool(this);
- this.methodPool = new BuilderMethodPool(this);
- this.protoPool = new BuilderProtoPool(this);
- this.classPool = new BuilderClassPool();
-
- this.typeListPool = new BuilderTypeListPool(this);
- this.annotationPool = new BuilderAnnotationPool(this);
- this.annotationSetPool = new BuilderAnnotationSetPool(this);
- }
-
- @Nonnull Set<? extends BuilderAnnotationElement> internAnnotationElements(
- @Nonnull Set<? extends AnnotationElement> elements) {
- return ImmutableSet.copyOf(
- Iterators.transform(elements.iterator(),
- new Function<AnnotationElement, BuilderAnnotationElement>() {
- @Nullable @Override
- public BuilderAnnotationElement apply(AnnotationElement input) {
- return internAnnotationElement(input);
- }
- }));
- }
-
- @Nonnull private BuilderAnnotationElement internAnnotationElement(@Nonnull AnnotationElement annotationElement) {
- return new BuilderAnnotationElement(stringPool.internString(annotationElement.getName()),
- internEncodedValue(annotationElement.getValue()));
- }
-
- @Nullable BuilderEncodedValue internNullableEncodedValue(@Nullable EncodedValue encodedValue) {
- if (encodedValue == null) {
- return null;
- }
- return internEncodedValue(encodedValue);
- }
-
- @Nonnull private BuilderEncodedValue internEncodedValue(@Nonnull EncodedValue encodedValue) {
- switch (encodedValue.getValueType()) {
- case ValueType.ANNOTATION:
- return internAnnotationEncodedValue((AnnotationEncodedValue)encodedValue);
- case ValueType.ARRAY:
- return internArrayEncodedValue((ArrayEncodedValue)encodedValue);
- case ValueType.BOOLEAN:
- boolean value = ((BooleanEncodedValue)encodedValue).getValue();
- return value?BuilderBooleanEncodedValue.TRUE_VALUE:BuilderBooleanEncodedValue.FALSE_VALUE;
- case ValueType.BYTE:
- return new BuilderByteEncodedValue(((ByteEncodedValue)encodedValue).getValue());
- case ValueType.CHAR:
- return new BuilderCharEncodedValue(((CharEncodedValue)encodedValue).getValue());
- case ValueType.DOUBLE:
- return new BuilderDoubleEncodedValue(((DoubleEncodedValue)encodedValue).getValue());
- case ValueType.ENUM:
- return internEnumEncodedValue((EnumEncodedValue)encodedValue);
- case ValueType.FIELD:
- return internFieldEncodedValue((FieldEncodedValue)encodedValue);
- case ValueType.FLOAT:
- return new BuilderFloatEncodedValue(((FloatEncodedValue)encodedValue).getValue());
- case ValueType.INT:
- return new BuilderIntEncodedValue(((IntEncodedValue)encodedValue).getValue());
- case ValueType.LONG:
- return new BuilderLongEncodedValue(((LongEncodedValue)encodedValue).getValue());
- case ValueType.METHOD:
- return internMethodEncodedValue((MethodEncodedValue)encodedValue);
- case ValueType.NULL:
- return BuilderNullEncodedValue.INSTANCE;
- case ValueType.SHORT:
- return new BuilderShortEncodedValue(((ShortEncodedValue)encodedValue).getValue());
- case ValueType.STRING:
- return internStringEncodedValue((StringEncodedValue)encodedValue);
- case ValueType.TYPE:
- return internTypeEncodedValue((TypeEncodedValue)encodedValue);
- default:
- throw new ExceptionWithContext("Unexpected encoded value type: %d", encodedValue.getValueType());
- }
- }
-
- @Nonnull private BuilderAnnotationEncodedValue internAnnotationEncodedValue(@Nonnull AnnotationEncodedValue value) {
- return new BuilderAnnotationEncodedValue(
- typePool.internType(value.getType()),
- internAnnotationElements(value.getElements()));
- }
-
- @Nonnull private BuilderArrayEncodedValue internArrayEncodedValue(@Nonnull ArrayEncodedValue value) {
- return new BuilderArrayEncodedValue(
- ImmutableList.copyOf(
- Iterators.transform(value.getValue().iterator(),
- new Function<EncodedValue, BuilderEncodedValue>() {
- @Nullable @Override public BuilderEncodedValue apply(EncodedValue input) {
- return internEncodedValue(input);
- }
- })));
- }
-
- @Nonnull private BuilderEnumEncodedValue internEnumEncodedValue(@Nonnull EnumEncodedValue value) {
- return new BuilderEnumEncodedValue(fieldPool.internField(value.getValue()));
- }
-
- @Nonnull private BuilderFieldEncodedValue internFieldEncodedValue(@Nonnull FieldEncodedValue value) {
- return new BuilderFieldEncodedValue(fieldPool.internField(value.getValue()));
- }
-
- @Nonnull private BuilderMethodEncodedValue internMethodEncodedValue(@Nonnull MethodEncodedValue value) {
- return new BuilderMethodEncodedValue(methodPool.internMethod(value.getValue()));
- }
-
- @Nonnull private BuilderStringEncodedValue internStringEncodedValue(@Nonnull StringEncodedValue string) {
- return new BuilderStringEncodedValue(stringPool.internString(string.getValue()));
- }
-
- @Nonnull private BuilderTypeEncodedValue internTypeEncodedValue(@Nonnull TypeEncodedValue type) {
- return new BuilderTypeEncodedValue(typePool.internType(type.getValue()));
- }
-}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderFieldPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderFieldPool.java
index 7a90649f..3bc65cd8 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderFieldPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderFieldPool.java
@@ -41,14 +41,13 @@ import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
-public class BuilderFieldPool
+public class BuilderFieldPool extends BaseBuilderPool
implements FieldSection<BuilderStringReference, BuilderTypeReference, BuilderFieldReference, BuilderField> {
- @Nonnull private final BuilderContext context;
@Nonnull private final ConcurrentMap<FieldReference, BuilderFieldReference> internedItems =
Maps.newConcurrentMap();
- BuilderFieldPool(@Nonnull BuilderContext context) {
- this.context = context;
+ public BuilderFieldPool(@Nonnull DexBuilder dexBuilder) {
+ super(dexBuilder);
}
@Nonnull BuilderFieldReference internField(@Nonnull String definingClass, String name, String type) {
@@ -63,9 +62,9 @@ public class BuilderFieldPool
}
BuilderFieldReference dexPoolFieldReference = new BuilderFieldReference(
- context.typePool.internType(fieldReference.getDefiningClass()),
- context.stringPool.internString(fieldReference.getName()),
- context.typePool.internType(fieldReference.getType()));
+ dexBuilder.typeSection.internType(fieldReference.getDefiningClass()),
+ dexBuilder.stringSection.internString(fieldReference.getName()),
+ dexBuilder.typeSection.internType(fieldReference.getType()));
ret = internedItems.putIfAbsent(dexPoolFieldReference, dexPoolFieldReference);
return ret==null?dexPoolFieldReference:ret;
}
@@ -104,4 +103,8 @@ public class BuilderFieldPool
}
};
}
+
+ @Override public int getItemCount() {
+ return internedItems.size();
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderMethodPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderMethodPool.java
index 2c5dd816..7f937fdc 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderMethodPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderMethodPool.java
@@ -42,14 +42,13 @@ import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
-class BuilderMethodPool implements MethodSection<BuilderStringReference, BuilderTypeReference,
+class BuilderMethodPool extends BaseBuilderPool implements MethodSection<BuilderStringReference, BuilderTypeReference,
BuilderMethodProtoReference, BuilderMethodReference, BuilderMethod>{
- @Nonnull private final BuilderContext context;
@Nonnull private final ConcurrentMap<MethodReference, BuilderMethodReference> internedItems =
Maps.newConcurrentMap();
- BuilderMethodPool(@Nonnull BuilderContext context) {
- this.context = context;
+ public BuilderMethodPool(@Nonnull DexBuilder dexBuilder) {
+ super(dexBuilder);
}
@Nonnull public BuilderMethodReference internMethod(@Nonnull MethodReference methodReference) {
@@ -59,9 +58,9 @@ class BuilderMethodPool implements MethodSection<BuilderStringReference, Builder
}
BuilderMethodReference dexPoolMethodReference = new BuilderMethodReference(
- context.typePool.internType(methodReference.getDefiningClass()),
- context.stringPool.internString(methodReference.getName()),
- context.protoPool.internMethodProto(methodReference));
+ dexBuilder.typeSection.internType(methodReference.getDefiningClass()),
+ dexBuilder.stringSection.internString(methodReference.getName()),
+ dexBuilder.protoSection.internMethodProto(methodReference));
ret = internedItems.putIfAbsent(dexPoolMethodReference, dexPoolMethodReference);
return ret==null?dexPoolMethodReference:ret;
}
@@ -72,6 +71,10 @@ class BuilderMethodPool implements MethodSection<BuilderStringReference, Builder
return internMethod(new MethodKey(definingClass, name, parameters, returnType));
}
+ @Nonnull @Override public BuilderMethodReference getMethodReference(@Nonnull BuilderMethod builderMethod) {
+ return builderMethod.methodReference;
+ }
+
@Nonnull @Override
public BuilderTypeReference getDefiningClass(@Nonnull BuilderMethodReference key) {
return key.definingClass;
@@ -112,6 +115,10 @@ class BuilderMethodPool implements MethodSection<BuilderStringReference, Builder
};
}
+ @Override public int getItemCount() {
+ return internedItems.size();
+ }
+
private static class MethodKey extends BaseMethodReference implements MethodReference {
@Nonnull private final String definingClass;
@Nonnull private final String name;
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderProtoPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderProtoPool.java
index de19fa30..969f2433 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderProtoPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderProtoPool.java
@@ -44,14 +44,13 @@ import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
-class BuilderProtoPool
+class BuilderProtoPool extends BaseBuilderPool
implements ProtoSection<BuilderStringReference, BuilderTypeReference, BuilderMethodProtoReference, BuilderTypeList> {
- @Nonnull private final BuilderContext context;
@Nonnull private final ConcurrentMap<MethodProtoReference, BuilderMethodProtoReference> internedItems =
Maps.newConcurrentMap();
- BuilderProtoPool(@Nonnull BuilderContext context) {
- this.context = context;
+ public BuilderProtoPool(@Nonnull DexBuilder dexBuilder) {
+ super(dexBuilder);
}
@Nonnull public BuilderMethodProtoReference internMethodProto(@Nonnull MethodProtoReference methodProto) {
@@ -61,10 +60,10 @@ class BuilderProtoPool
}
BuilderMethodProtoReference protoReference = new BuilderMethodProtoReference(
- context.stringPool.internString(MethodUtil.getShorty(
+ dexBuilder.stringSection.internString(MethodUtil.getShorty(
methodProto.getParameterTypes(), methodProto.getReturnType())),
- context.typeListPool.internTypeList(methodProto.getParameterTypes()),
- context.typePool.internType(methodProto.getReturnType()));
+ dexBuilder.typeListSection.internTypeList(methodProto.getParameterTypes()),
+ dexBuilder.typeSection.internType(methodProto.getReturnType()));
ret = internedItems.putIfAbsent(protoReference, protoReference);
return ret==null?protoReference:ret;
}
@@ -103,4 +102,8 @@ class BuilderProtoPool
}
};
}
+
+ @Override public int getItemCount() {
+ return internedItems.size();
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderStringPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderStringPool.java
index 6b60e9f8..95fe86fb 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderStringPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderStringPool.java
@@ -86,4 +86,8 @@ class BuilderStringPool implements StringSection<BuilderStringReference, Builder
}
};
}
+
+ @Override public int getItemCount() {
+ return internedItems.size();
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypeListPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypeListPool.java
index 7b189fdb..604e39ce 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypeListPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypeListPool.java
@@ -45,13 +45,12 @@ import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
-class BuilderTypeListPool implements TypeListSection<BuilderTypeReference, BuilderTypeList> {
- @Nonnull private final BuilderContext context;
+class BuilderTypeListPool extends BaseBuilderPool implements TypeListSection<BuilderTypeReference, BuilderTypeList> {
@Nonnull private final ConcurrentMap<List<? extends CharSequence>, BuilderTypeList> internedItems =
Maps.newConcurrentMap();
- BuilderTypeListPool(@Nonnull BuilderContext context) {
- this.context = context;
+ public BuilderTypeListPool(@Nonnull DexBuilder dexBuilder) {
+ super(dexBuilder);
}
@Nonnull public BuilderTypeList internTypeList(@Nullable List<? extends CharSequence> types) {
@@ -67,7 +66,7 @@ class BuilderTypeListPool implements TypeListSection<BuilderTypeReference, Build
BuilderTypeList typeList = new BuilderTypeList(
ImmutableList.copyOf(Iterables.transform(types, new Function<CharSequence, BuilderTypeReference>() {
@Nonnull @Override public BuilderTypeReference apply(CharSequence input) {
- return context.typePool.internType(input.toString());
+ return dexBuilder.typeSection.internType(input.toString());
}
})));
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypePool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypePool.java
index 29871fc9..b9476de0 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypePool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderTypePool.java
@@ -41,12 +41,12 @@ import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
-class BuilderTypePool implements TypeSection<BuilderStringReference, BuilderTypeReference, BuilderTypeReference> {
- @Nonnull private final BuilderContext context;
+class BuilderTypePool extends BaseBuilderPool
+ implements TypeSection<BuilderStringReference, BuilderTypeReference, BuilderTypeReference> {
@Nonnull private final ConcurrentMap<String, BuilderTypeReference> internedItems = Maps.newConcurrentMap();
- BuilderTypePool(@Nonnull BuilderContext context) {
- this.context = context;
+ public BuilderTypePool(@Nonnull DexBuilder dexBuilder) {
+ super(dexBuilder);
}
@Nonnull public BuilderTypeReference internType(@Nonnull String type) {
@@ -54,7 +54,7 @@ class BuilderTypePool implements TypeSection<BuilderStringReference, BuilderType
if (ret != null) {
return ret;
}
- BuilderStringReference stringRef = context.stringPool.internString(type);
+ BuilderStringReference stringRef = dexBuilder.stringSection.internString(type);
BuilderTypeReference typeReference = new BuilderTypeReference(stringRef);
ret = internedItems.putIfAbsent(type, typeReference);
return ret==null?typeReference:ret;
@@ -92,4 +92,8 @@ class BuilderTypePool implements TypeSection<BuilderStringReference, BuilderType
}
};
}
+
+ @Override public int getItemCount() {
+ return internedItems.size();
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/DexBuilder.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/DexBuilder.java
index b7507fa6..25938fe0 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/DexBuilder.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/DexBuilder.java
@@ -33,11 +33,13 @@ package org.jf.dexlib2.writer.builder;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.ValueType;
import org.jf.dexlib2.iface.Annotation;
+import org.jf.dexlib2.iface.AnnotationElement;
import org.jf.dexlib2.iface.MethodImplementation;
import org.jf.dexlib2.iface.MethodParameter;
import org.jf.dexlib2.iface.reference.*;
@@ -56,32 +58,16 @@ import java.util.Set;
public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringReference, BuilderTypeReference,
BuilderTypeReference, BuilderMethodProtoReference, BuilderFieldReference, BuilderMethodReference,
BuilderClassDef, BuilderAnnotation, BuilderAnnotationSet, BuilderTypeList, BuilderField, BuilderMethod,
- BuilderEncodedValue, BuilderAnnotationElement> {
+ BuilderEncodedValue, BuilderAnnotationElement, BuilderStringPool, BuilderTypePool, BuilderProtoPool,
+ BuilderFieldPool, BuilderMethodPool, BuilderClassPool, BuilderTypeListPool, BuilderAnnotationPool,
+ BuilderAnnotationSetPool> {
- @Nonnull private final BuilderContext context;
-
- @Nonnull public static DexBuilder makeDexBuilder() {
- BuilderContext context = new BuilderContext();
- return new DexBuilder(Opcodes.forApi(20), context);
- }
-
- @Deprecated
- @Nonnull
- public static DexBuilder makeDexBuilder(int api) {
- BuilderContext context = new BuilderContext();
- return new DexBuilder(Opcodes.forApi(api), context);
- }
-
- @Nonnull public static DexBuilder makeDexBuilder(@Nonnull Opcodes opcodes) {
- BuilderContext context = new BuilderContext();
- return new DexBuilder(opcodes, context);
+ public DexBuilder(@Nonnull Opcodes opcodes) {
+ super(opcodes);
}
- private DexBuilder(@Nonnull Opcodes opcodes, @Nonnull BuilderContext context) {
- super(opcodes, context.stringPool, context.typePool, context.protoPool,
- context.fieldPool, context.methodPool, context.classPool, context.typeListPool, context.annotationPool,
- context.annotationSetPool);
- this.context = context;
+ @Nonnull @Override protected SectionProvider getSectionProvider() {
+ return new DexBuilderSectionProvider();
}
@Nonnull public BuilderField internField(@Nonnull String definingClass,
@@ -90,10 +76,10 @@ public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringR
int accessFlags,
@Nullable EncodedValue initialValue,
@Nonnull Set<? extends Annotation> annotations) {
- return new BuilderField(context.fieldPool.internField(definingClass, name, type),
+ return new BuilderField(fieldSection.internField(definingClass, name, type),
accessFlags,
- context.internNullableEncodedValue(initialValue),
- context.annotationSetPool.internAnnotationSet(annotations));
+ internNullableEncodedValue(initialValue),
+ annotationSetSection.internAnnotationSet(annotations));
}
@Nonnull public BuilderMethod internMethod(@Nonnull String definingClass,
@@ -106,10 +92,10 @@ public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringR
if (parameters == null) {
parameters = ImmutableList.of();
}
- return new BuilderMethod(context.methodPool.internMethod(definingClass, name, parameters, returnType),
+ return new BuilderMethod(methodSection.internMethod(definingClass, name, parameters, returnType),
internMethodParameters(parameters),
accessFlags,
- context.annotationSetPool.internAnnotationSet(annotations),
+ annotationSetSection.internAnnotationSet(annotations),
methodImplementation);
}
@@ -136,18 +122,18 @@ public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringR
}
}
- return context.classPool.internClass(new BuilderClassDef(context.typePool.internType(type),
+ return classSection.internClass(new BuilderClassDef(typeSection.internType(type),
accessFlags,
- context.typePool.internNullableType(superclass),
- context.typeListPool.internTypeList(interfaces),
- context.stringPool.internNullableString(sourceFile),
- context.annotationSetPool.internAnnotationSet(annotations),
+ typeSection.internNullableType(superclass),
+ typeListSection.internTypeList(interfaces),
+ stringSection.internNullableString(sourceFile),
+ annotationSetSection.internAnnotationSet(annotations),
fields,
methods));
}
@Nonnull public BuilderStringReference internStringReference(@Nonnull String string) {
- return context.stringPool.internString(string);
+ return stringSection.internString(string);
}
@Nullable public BuilderStringReference internNullableStringReference(@Nullable String string) {
@@ -158,7 +144,7 @@ public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringR
}
@Nonnull public BuilderTypeReference internTypeReference(@Nonnull String type) {
- return context.typePool.internType(type);
+ return typeSection.internType(type);
}
@Nullable public BuilderTypeReference internNullableTypeReference(@Nullable String type) {
@@ -169,15 +155,15 @@ public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringR
}
@Nonnull public BuilderFieldReference internFieldReference(@Nonnull FieldReference field) {
- return context.fieldPool.internField(field);
+ return fieldSection.internField(field);
}
@Nonnull public BuilderMethodReference internMethodReference(@Nonnull MethodReference method) {
- return context.methodPool.internMethod(method);
+ return methodSection.internMethod(method);
}
@Nonnull public BuilderMethodProtoReference internMethodProtoReference(@Nonnull MethodProtoReference methodProto) {
- return context.protoPool.internMethodProto(methodProto);
+ return protoSection.internMethodProto(methodProto);
}
@Nonnull public BuilderReference internReference(@Nonnull Reference reference) {
@@ -214,9 +200,9 @@ public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringR
@Nonnull private BuilderMethodParameter internMethodParameter(@Nonnull MethodParameter methodParameter) {
return new BuilderMethodParameter(
- context.typePool.internType(methodParameter.getType()),
- context.stringPool.internNullableString(methodParameter.getName()),
- context.annotationSetPool.internAnnotationSet(methodParameter.getAnnotations()));
+ typeSection.internType(methodParameter.getType()),
+ stringSection.internNullableString(methodParameter.getName()),
+ annotationSetSection.internAnnotationSet(methodParameter.getAnnotations()));
}
@Override protected void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,
@@ -276,4 +262,143 @@ public class DexBuilder extends DexWriter<BuilderStringReference, BuilderStringR
throw new ExceptionWithContext("Unrecognized value type: %d", encodedValue.getValueType());
}
}
+
+ @Nonnull Set<? extends BuilderAnnotationElement> internAnnotationElements(
+ @Nonnull Set<? extends AnnotationElement> elements) {
+ return ImmutableSet.copyOf(
+ Iterators.transform(elements.iterator(),
+ new Function<AnnotationElement, BuilderAnnotationElement>() {
+ @Nullable @Override
+ public BuilderAnnotationElement apply(AnnotationElement input) {
+ return internAnnotationElement(input);
+ }
+ }));
+ }
+
+ @Nonnull private BuilderAnnotationElement internAnnotationElement(@Nonnull AnnotationElement annotationElement) {
+ return new BuilderAnnotationElement(stringSection.internString(annotationElement.getName()),
+ internEncodedValue(annotationElement.getValue()));
+ }
+
+ @Nullable BuilderEncodedValue internNullableEncodedValue(@Nullable EncodedValue encodedValue) {
+ if (encodedValue == null) {
+ return null;
+ }
+ return internEncodedValue(encodedValue);
+ }
+
+ @Nonnull private BuilderEncodedValue internEncodedValue(@Nonnull EncodedValue encodedValue) {
+ switch (encodedValue.getValueType()) {
+ case ValueType.ANNOTATION:
+ return internAnnotationEncodedValue((AnnotationEncodedValue)encodedValue);
+ case ValueType.ARRAY:
+ return internArrayEncodedValue((ArrayEncodedValue)encodedValue);
+ case ValueType.BOOLEAN:
+ boolean value = ((BooleanEncodedValue)encodedValue).getValue();
+ return value?BuilderBooleanEncodedValue.TRUE_VALUE:BuilderBooleanEncodedValue.FALSE_VALUE;
+ case ValueType.BYTE:
+ return new BuilderByteEncodedValue(((ByteEncodedValue)encodedValue).getValue());
+ case ValueType.CHAR:
+ return new BuilderCharEncodedValue(((CharEncodedValue)encodedValue).getValue());
+ case ValueType.DOUBLE:
+ return new BuilderDoubleEncodedValue(((DoubleEncodedValue)encodedValue).getValue());
+ case ValueType.ENUM:
+ return internEnumEncodedValue((EnumEncodedValue)encodedValue);
+ case ValueType.FIELD:
+ return internFieldEncodedValue((FieldEncodedValue)encodedValue);
+ case ValueType.FLOAT:
+ return new BuilderFloatEncodedValue(((FloatEncodedValue)encodedValue).getValue());
+ case ValueType.INT:
+ return new BuilderIntEncodedValue(((IntEncodedValue)encodedValue).getValue());
+ case ValueType.LONG:
+ return new BuilderLongEncodedValue(((LongEncodedValue)encodedValue).getValue());
+ case ValueType.METHOD:
+ return internMethodEncodedValue((MethodEncodedValue)encodedValue);
+ case ValueType.NULL:
+ return BuilderNullEncodedValue.INSTANCE;
+ case ValueType.SHORT:
+ return new BuilderShortEncodedValue(((ShortEncodedValue)encodedValue).getValue());
+ case ValueType.STRING:
+ return internStringEncodedValue((StringEncodedValue)encodedValue);
+ case ValueType.TYPE:
+ return internTypeEncodedValue((TypeEncodedValue)encodedValue);
+ default:
+ throw new ExceptionWithContext("Unexpected encoded value type: %d", encodedValue.getValueType());
+ }
+ }
+
+ @Nonnull private BuilderAnnotationEncodedValue internAnnotationEncodedValue(@Nonnull AnnotationEncodedValue value) {
+ return new BuilderAnnotationEncodedValue(
+ typeSection.internType(value.getType()),
+ internAnnotationElements(value.getElements()));
+ }
+
+ @Nonnull private BuilderArrayEncodedValue internArrayEncodedValue(@Nonnull ArrayEncodedValue value) {
+ return new BuilderArrayEncodedValue(
+ ImmutableList.copyOf(
+ Iterators.transform(value.getValue().iterator(),
+ new Function<EncodedValue, BuilderEncodedValue>() {
+ @Nullable @Override public BuilderEncodedValue apply(EncodedValue input) {
+ return internEncodedValue(input);
+ }
+ })));
+ }
+
+ @Nonnull private BuilderEnumEncodedValue internEnumEncodedValue(@Nonnull EnumEncodedValue value) {
+ return new BuilderEnumEncodedValue(fieldSection.internField(value.getValue()));
+ }
+
+ @Nonnull private BuilderFieldEncodedValue internFieldEncodedValue(@Nonnull FieldEncodedValue value) {
+ return new BuilderFieldEncodedValue(fieldSection.internField(value.getValue()));
+ }
+
+ @Nonnull private BuilderMethodEncodedValue internMethodEncodedValue(@Nonnull MethodEncodedValue value) {
+ return new BuilderMethodEncodedValue(methodSection.internMethod(value.getValue()));
+ }
+
+ @Nonnull private BuilderStringEncodedValue internStringEncodedValue(@Nonnull StringEncodedValue string) {
+ return new BuilderStringEncodedValue(stringSection.internString(string.getValue()));
+ }
+
+ @Nonnull private BuilderTypeEncodedValue internTypeEncodedValue(@Nonnull TypeEncodedValue type) {
+ return new BuilderTypeEncodedValue(typeSection.internType(type.getValue()));
+ }
+
+ protected class DexBuilderSectionProvider extends SectionProvider {
+ @Nonnull @Override public BuilderStringPool getStringSection() {
+ return new BuilderStringPool();
+ }
+
+ @Nonnull @Override public BuilderTypePool getTypeSection() {
+ return new BuilderTypePool(DexBuilder.this);
+ }
+
+ @Nonnull @Override public BuilderProtoPool getProtoSection() {
+ return new BuilderProtoPool(DexBuilder.this);
+ }
+
+ @Nonnull @Override public BuilderFieldPool getFieldSection() {
+ return new BuilderFieldPool(DexBuilder.this);
+ }
+
+ @Nonnull @Override public BuilderMethodPool getMethodSection() {
+ return new BuilderMethodPool(DexBuilder.this);
+ }
+
+ @Nonnull @Override public BuilderClassPool getClassSection() {
+ return new BuilderClassPool(DexBuilder.this);
+ }
+
+ @Nonnull @Override public BuilderTypeListPool getTypeListSection() {
+ return new BuilderTypeListPool(DexBuilder.this);
+ }
+
+ @Nonnull @Override public BuilderAnnotationPool getAnnotationSection() {
+ return new BuilderAnnotationPool(DexBuilder.this);
+ }
+
+ @Nonnull @Override public BuilderAnnotationSetPool getAnnotationSetSection() {
+ return new BuilderAnnotationSetPool(DexBuilder.this);
+ }
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationPool.java
index c4cfa390..b2db8890 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationPool.java
@@ -41,26 +41,18 @@ import java.util.Collection;
public class AnnotationPool extends BaseOffsetPool<Annotation>
implements AnnotationSection<CharSequence, CharSequence, Annotation, AnnotationElement, EncodedValue> {
- @Nonnull StringPool stringPool;
- @Nonnull TypePool typePool;
- @Nonnull FieldPool fieldPool;
- @Nonnull MethodPool methodPool;
- public AnnotationPool(@Nonnull StringPool stringPool, @Nonnull TypePool typePool,
- @Nonnull FieldPool fieldPool, @Nonnull MethodPool methodPool) {
- this.stringPool = stringPool;
- this.typePool = typePool;
- this.fieldPool = fieldPool;
- this.methodPool = methodPool;
+ public AnnotationPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
}
public void intern(@Nonnull Annotation annotation) {
Integer prev = internedItems.put(annotation, 0);
if (prev == null) {
- typePool.intern(annotation.getType());
+ dexPool.typeSection.intern(annotation.getType());
for (AnnotationElement element: annotation.getElements()) {
- stringPool.intern(element.getName());
- DexPool.internEncodedValue(element.getValue(), stringPool, typePool, fieldPool, methodPool);
+ dexPool.stringSection.intern(element.getName());
+ dexPool.internEncodedValue(element.getValue());
}
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationSetPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationSetPool.java
index 6512dcb4..2170b95b 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationSetPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/AnnotationSetPool.java
@@ -40,10 +40,9 @@ import java.util.Set;
public class AnnotationSetPool extends BaseNullableOffsetPool<Set<? extends Annotation>>
implements AnnotationSetSection<Annotation, Set<? extends Annotation>> {
- @Nonnull private final AnnotationPool annotationPool;
- public AnnotationSetPool(@Nonnull AnnotationPool annotationPool) {
- this.annotationPool = annotationPool;
+ public AnnotationSetPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
}
public void intern(@Nonnull Set<? extends Annotation> annotationSet) {
@@ -51,7 +50,7 @@ public class AnnotationSetPool extends BaseNullableOffsetPool<Set<? extends Anno
Integer prev = internedItems.put(annotationSet, 0);
if (prev == null) {
for (Annotation annotation: annotationSet) {
- annotationPool.intern(annotation);
+ dexPool.annotationSection.intern(annotation);
}
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseIndexPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseIndexPool.java
index 01109ad4..c07dcf1a 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseIndexPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseIndexPool.java
@@ -31,7 +31,6 @@
package org.jf.dexlib2.writer.pool;
-import com.google.common.collect.Maps;
import org.jf.dexlib2.writer.IndexSection;
import org.jf.util.ExceptionWithContext;
@@ -39,8 +38,11 @@ import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Map;
-public abstract class BaseIndexPool<Key> implements IndexSection<Key> {
- @Nonnull protected final Map<Key, Integer> internedItems = Maps.newHashMap();
+public abstract class BaseIndexPool<Key> extends BasePool<Key, Integer> implements IndexSection<Key> {
+
+ public BaseIndexPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
+ }
@Nonnull @Override public Collection<? extends Map.Entry<? extends Key, Integer>> getItems() {
return internedItems.entrySet();
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseNullableOffsetPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseNullableOffsetPool.java
index ed9dbb6a..b04060a3 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseNullableOffsetPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseNullableOffsetPool.java
@@ -34,10 +34,16 @@ package org.jf.dexlib2.writer.pool;
import org.jf.dexlib2.writer.DexWriter;
import org.jf.dexlib2.writer.NullableOffsetSection;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public abstract class BaseNullableOffsetPool<Key> extends BaseOffsetPool<Key>
implements NullableOffsetSection<Key> {
+
+ public BaseNullableOffsetPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
+ }
+
@Override public int getNullableItemOffset(@Nullable Key key) {
if (key == null) {
return DexWriter.NO_OFFSET;
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseOffsetPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseOffsetPool.java
index e66a50ad..789c9547 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseOffsetPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseOffsetPool.java
@@ -31,7 +31,6 @@
package org.jf.dexlib2.writer.pool;
-import com.google.common.collect.Maps;
import org.jf.dexlib2.writer.OffsetSection;
import org.jf.util.ExceptionWithContext;
@@ -39,8 +38,11 @@ import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Map;
-public abstract class BaseOffsetPool<Key> implements OffsetSection<Key> {
- @Nonnull protected final Map<Key, Integer> internedItems = Maps.newHashMap();
+public abstract class BaseOffsetPool<Key> extends BasePool<Key, Integer> implements OffsetSection<Key> {
+
+ public BaseOffsetPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
+ }
@Nonnull @Override public Collection<? extends Map.Entry<? extends Key, Integer>> getItems() {
return internedItems.entrySet();
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BasePool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BasePool.java
new file mode 100644
index 00000000..4fa18102
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BasePool.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.writer.pool;
+
+import com.google.common.collect.Maps;
+
+import javax.annotation.Nonnull;
+import java.util.Iterator;
+import java.util.Map;
+
+public class BasePool<Key, Value> implements Markable {
+ @Nonnull protected final DexPool dexPool;
+ @Nonnull protected final Map<Key, Value> internedItems = Maps.newLinkedHashMap();
+ private int markedItemCount = -1;
+
+ public BasePool(@Nonnull DexPool dexPool) {
+ this.dexPool = dexPool;
+ }
+
+ public void mark() {
+ markedItemCount = internedItems.size();
+ }
+
+ public void reset() {
+ if (markedItemCount < 0) {
+ throw new IllegalStateException("mark() must be called before calling reset()");
+ }
+
+ if (markedItemCount == internedItems.size()) {
+ return;
+ }
+
+ Iterator<Key> keys = internedItems.keySet().iterator();
+ for (int i=0; i<markedItemCount; i++) {
+ keys.next();
+ }
+ while (keys.hasNext()) {
+ keys.next();
+ keys.remove();
+ }
+ }
+
+ public int getItemCount() {
+ return internedItems.size();
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java
index 0389973a..7c1d6811 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java
@@ -58,30 +58,12 @@ import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
-public class ClassPool implements ClassSection<CharSequence, CharSequence,
+public class ClassPool extends BasePool<String, PoolClassDef> implements ClassSection<CharSequence, CharSequence,
TypeListPool.Key<? extends Collection<? extends CharSequence>>, PoolClassDef, Field, PoolMethod,
Set<? extends Annotation>, EncodedValue> {
- @Nonnull private HashMap<String, PoolClassDef> internedItems = Maps.newHashMap();
-
- @Nonnull private final StringPool stringPool;
- @Nonnull private final TypePool typePool;
- @Nonnull private final FieldPool fieldPool;
- @Nonnull private final MethodPool methodPool;
- @Nonnull private final AnnotationSetPool annotationSetPool;
- @Nonnull private final TypeListPool typeListPool;
-
- public ClassPool(@Nonnull StringPool stringPool,
- @Nonnull TypePool typePool,
- @Nonnull FieldPool fieldPool,
- @Nonnull MethodPool methodPool,
- @Nonnull AnnotationSetPool annotationSetPool,
- @Nonnull TypeListPool typeListPool) {
- this.stringPool = stringPool;
- this.typePool = typePool;
- this.fieldPool = fieldPool;
- this.methodPool = methodPool;
- this.annotationSetPool = annotationSetPool;
- this.typeListPool = typeListPool;
+
+ public ClassPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
}
public void intern(@Nonnull ClassDef classDef) {
@@ -92,10 +74,10 @@ public class ClassPool implements ClassSection<CharSequence, CharSequence,
throw new ExceptionWithContext("Class %s has already been interned", poolClassDef.getType());
}
- typePool.intern(poolClassDef.getType());
- typePool.internNullable(poolClassDef.getSuperclass());
- typeListPool.intern(poolClassDef.getInterfaces());
- stringPool.internNullable(poolClassDef.getSourceFile());
+ dexPool.typeSection.intern(poolClassDef.getType());
+ dexPool.typeSection.internNullable(poolClassDef.getSuperclass());
+ dexPool.typeListSection.intern(poolClassDef.getInterfaces());
+ dexPool.stringSection.internNullable(poolClassDef.getSourceFile());
HashSet<String> fields = new HashSet<String>();
for (Field field: poolClassDef.getFields()) {
@@ -104,14 +86,14 @@ public class ClassPool implements ClassSection<CharSequence, CharSequence,
throw new ExceptionWithContext("Multiple definitions for field %s->%s",
poolClassDef.getType(), fieldDescriptor);
}
- fieldPool.intern(field);
+ dexPool.fieldSection.intern(field);
EncodedValue initialValue = field.getInitialValue();
if (initialValue != null) {
- DexPool.internEncodedValue(initialValue, stringPool, typePool, fieldPool, methodPool);
+ dexPool.internEncodedValue(initialValue);
}
- annotationSetPool.intern(field.getAnnotations());
+ dexPool.annotationSetSection.intern(field.getAnnotations());
}
HashSet<String> methods = new HashSet<String>();
@@ -121,17 +103,17 @@ public class ClassPool implements ClassSection<CharSequence, CharSequence,
throw new ExceptionWithContext("Multiple definitions for method %s->%s",
poolClassDef.getType(), methodDescriptor);
}
- methodPool.intern(method);
+ dexPool.methodSection.intern(method);
internCode(method);
internDebug(method);
- annotationSetPool.intern(method.getAnnotations());
+ dexPool.annotationSetSection.intern(method.getAnnotations());
for (MethodParameter parameter: method.getParameters()) {
- annotationSetPool.intern(parameter.getAnnotations());
+ dexPool.annotationSetSection.intern(parameter.getAnnotations());
}
}
- annotationSetPool.intern(poolClassDef.getAnnotations());
+ dexPool.annotationSetSection.intern(poolClassDef.getAnnotations());
}
private void internCode(@Nonnull Method method) {
@@ -146,16 +128,16 @@ public class ClassPool implements ClassSection<CharSequence, CharSequence,
Reference reference = ((ReferenceInstruction)instruction).getReference();
switch (instruction.getOpcode().referenceType) {
case ReferenceType.STRING:
- stringPool.intern((StringReference)reference);
+ dexPool.stringSection.intern((StringReference)reference);
break;
case ReferenceType.TYPE:
- typePool.intern((TypeReference)reference);
+ dexPool.typeSection.intern((TypeReference)reference);
break;
case ReferenceType.FIELD:
- fieldPool.intern((FieldReference) reference);
+ dexPool.fieldSection.intern((FieldReference) reference);
break;
case ReferenceType.METHOD:
- methodPool.intern((MethodReference)reference);
+ dexPool.methodSection.intern((MethodReference)reference);
break;
default:
throw new ExceptionWithContext("Unrecognized reference type: %d",
@@ -172,7 +154,7 @@ public class ClassPool implements ClassSection<CharSequence, CharSequence,
for (TryBlock<? extends ExceptionHandler> tryBlock: methodImpl.getTryBlocks()) {
for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) {
- typePool.internNullable(handler.getExceptionType());
+ dexPool.typeSection.internNullable(handler.getExceptionType());
}
}
}
@@ -182,7 +164,7 @@ public class ClassPool implements ClassSection<CharSequence, CharSequence,
for (MethodParameter param: method.getParameters()) {
String paramName = param.getName();
if (paramName != null) {
- stringPool.intern(paramName);
+ dexPool.stringSection.intern(paramName);
}
}
@@ -192,12 +174,12 @@ public class ClassPool implements ClassSection<CharSequence, CharSequence,
switch (debugItem.getDebugItemType()) {
case DebugItemType.START_LOCAL:
StartLocal startLocal = (StartLocal)debugItem;
- stringPool.internNullable(startLocal.getName());
- typePool.internNullable(startLocal.getType());
- stringPool.internNullable(startLocal.getSignature());
+ dexPool.stringSection.internNullable(startLocal.getName());
+ dexPool.typeSection.internNullable(startLocal.getType());
+ dexPool.stringSection.internNullable(startLocal.getSignature());
break;
case DebugItemType.SET_SOURCE_FILE:
- stringPool.internNullable(((SetSourceFile) debugItem).getSourceFile());
+ dexPool.stringSection.internNullable(((SetSourceFile) debugItem).getSourceFile());
break;
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/DexPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/DexPool.java
index d12457a2..6d662ec3 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/DexPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/DexPool.java
@@ -39,7 +39,7 @@ import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.Field;
import org.jf.dexlib2.iface.reference.*;
import org.jf.dexlib2.iface.value.*;
-import org.jf.dexlib2.writer.DexWriter;
+import org.jf.dexlib2.writer.*;
import org.jf.dexlib2.writer.io.DexDataStore;
import org.jf.dexlib2.writer.io.FileDataStore;
import org.jf.util.ExceptionWithContext;
@@ -54,59 +54,69 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
MethodProtoReference, FieldReference, MethodReference, PoolClassDef,
Annotation, Set<? extends Annotation>,
TypeListPool.Key<? extends Collection<? extends CharSequence>>, Field, PoolMethod,
- EncodedValue, AnnotationElement> {
+ EncodedValue, AnnotationElement, StringPool, TypePool, ProtoPool, FieldPool, MethodPool, ClassPool,
+ TypeListPool, AnnotationPool, AnnotationSetPool> {
- @Nonnull
- public static DexPool makeDexPool() {
- return makeDexPool(Opcodes.forApi(20));
- }
-
- @Deprecated
- @Nonnull
- public static DexPool makeDexPool(int api) {
- return makeDexPool(Opcodes.forApi(api));
- }
+ private final Markable[] sections = new Markable[] {
+ stringSection, typeSection, protoSection, fieldSection, methodSection, classSection, typeListSection,
+ annotationSection, annotationSetSection
+ };
- @Nonnull
- public static DexPool makeDexPool(@Nonnull Opcodes opcodes) {
- StringPool stringPool = new StringPool();
- TypePool typePool = new TypePool(stringPool);
- FieldPool fieldPool = new FieldPool(stringPool, typePool);
- TypeListPool typeListPool = new TypeListPool(typePool);
- ProtoPool protoPool = new ProtoPool(stringPool, typePool, typeListPool);
- MethodPool methodPool = new MethodPool(stringPool, typePool, protoPool);
- AnnotationPool annotationPool = new AnnotationPool(stringPool, typePool, fieldPool, methodPool);
- AnnotationSetPool annotationSetPool = new AnnotationSetPool(annotationPool);
- ClassPool classPool = new ClassPool(stringPool, typePool, fieldPool, methodPool, annotationSetPool,
- typeListPool);
-
- return new DexPool(opcodes, stringPool, typePool, protoPool, fieldPool, methodPool, classPool, typeListPool,
- annotationPool, annotationSetPool);
+ public DexPool(Opcodes opcodes) {
+ super(opcodes);
}
- private DexPool(Opcodes opcodes, StringPool stringPool, TypePool typePool, ProtoPool protoPool, FieldPool fieldPool,
- MethodPool methodPool, ClassPool classPool, TypeListPool typeListPool,
- AnnotationPool annotationPool, AnnotationSetPool annotationSetPool) {
- super(opcodes, stringPool, typePool, protoPool, fieldPool, methodPool,
- classPool, typeListPool, annotationPool, annotationSetPool);
+ @Nonnull @Override protected SectionProvider getSectionProvider() {
+ return new DexPoolSectionProvider();
}
- public static void writeTo(@Nonnull DexDataStore dataStore, @Nonnull org.jf.dexlib2.iface.DexFile input) throws IOException {
- DexPool dexPool = makeDexPool();
+ public static void writeTo(@Nonnull DexDataStore dataStore, @Nonnull org.jf.dexlib2.iface.DexFile input)
+ throws IOException {
+ DexPool dexPool = new DexPool(input.getOpcodes());
for (ClassDef classDef: input.getClasses()) {
- ((ClassPool)dexPool.classSection).intern(classDef);
+ dexPool.internClass(classDef);
}
dexPool.writeTo(dataStore);
}
public static void writeTo(@Nonnull String path, @Nonnull org.jf.dexlib2.iface.DexFile input) throws IOException {
- DexPool dexPool = makeDexPool();
+ DexPool dexPool = new DexPool(input.getOpcodes());
for (ClassDef classDef: input.getClasses()) {
- ((ClassPool)dexPool.classSection).intern(classDef);
+ dexPool.internClass(classDef);
}
dexPool.writeTo(new FileDataStore(new File(path)));
}
+ /**
+ * Interns a class into this DexPool
+ * @param classDef The class to intern
+ */
+ public void internClass(ClassDef classDef) {
+ classSection.intern(classDef);
+ }
+
+ /**
+ * Creates a marked state that can be returned to by calling reset()
+ *
+ * This is useful to rollback the last added class if it causes a method/field/type overflow
+ */
+ public void mark() {
+ for (Markable section: sections) {
+ section.mark();
+ }
+ }
+
+ /**
+ * Resets to the last marked state
+ *
+ * This is useful to rollback the last added class if it causes a method/field/type overflow
+ */
+ public void reset() {
+ for (Markable section: sections) {
+ section.reset();
+ }
+ }
+
@Override protected void writeEncodedValue(@Nonnull InternalEncodedValueWriter writer,
@Nonnull EncodedValue encodedValue) throws IOException {
switch (encodedValue.getValueType()) {
@@ -165,40 +175,74 @@ public class DexPool extends DexWriter<CharSequence, StringReference, CharSequen
}
}
- public static void internEncodedValue(@Nonnull EncodedValue encodedValue,
- @Nonnull StringPool stringPool,
- @Nonnull TypePool typePool,
- @Nonnull FieldPool fieldPool,
- @Nonnull MethodPool methodPool) {
+ void internEncodedValue(@Nonnull EncodedValue encodedValue) {
switch (encodedValue.getValueType()) {
case ValueType.ANNOTATION:
AnnotationEncodedValue annotationEncodedValue = (AnnotationEncodedValue)encodedValue;
- typePool.intern(annotationEncodedValue.getType());
+ typeSection.intern(annotationEncodedValue.getType());
for (AnnotationElement element: annotationEncodedValue.getElements()) {
- stringPool.intern(element.getName());
- internEncodedValue(element.getValue(), stringPool, typePool, fieldPool, methodPool);
+ stringSection.intern(element.getName());
+ internEncodedValue(element.getValue());
}
break;
case ValueType.ARRAY:
for (EncodedValue element: ((ArrayEncodedValue)encodedValue).getValue()) {
- internEncodedValue(element, stringPool, typePool, fieldPool, methodPool);
+ internEncodedValue(element);
}
break;
case ValueType.STRING:
- stringPool.intern(((StringEncodedValue)encodedValue).getValue());
+ stringSection.intern(((StringEncodedValue)encodedValue).getValue());
break;
case ValueType.TYPE:
- typePool.intern(((TypeEncodedValue)encodedValue).getValue());
+ typeSection.intern(((TypeEncodedValue)encodedValue).getValue());
break;
case ValueType.ENUM:
- fieldPool.intern(((EnumEncodedValue)encodedValue).getValue());
+ fieldSection.intern(((EnumEncodedValue)encodedValue).getValue());
break;
case ValueType.FIELD:
- fieldPool.intern(((FieldEncodedValue)encodedValue).getValue());
+ fieldSection.intern(((FieldEncodedValue)encodedValue).getValue());
break;
case ValueType.METHOD:
- methodPool.intern(((MethodEncodedValue)encodedValue).getValue());
+ methodSection.intern(((MethodEncodedValue)encodedValue).getValue());
break;
}
}
+
+ protected class DexPoolSectionProvider extends SectionProvider {
+ @Nonnull @Override public StringPool getStringSection() {
+ return new StringPool(DexPool.this);
+ }
+
+ @Nonnull @Override public TypePool getTypeSection() {
+ return new TypePool(DexPool.this);
+ }
+
+ @Nonnull @Override public ProtoPool getProtoSection() {
+ return new ProtoPool(DexPool.this);
+ }
+
+ @Nonnull @Override public FieldPool getFieldSection() {
+ return new FieldPool(DexPool.this);
+ }
+
+ @Nonnull @Override public MethodPool getMethodSection() {
+ return new MethodPool(DexPool.this);
+ }
+
+ @Nonnull @Override public ClassPool getClassSection() {
+ return new ClassPool(DexPool.this);
+ }
+
+ @Nonnull @Override public TypeListPool getTypeListSection() {
+ return new TypeListPool(DexPool.this);
+ }
+
+ @Nonnull @Override public AnnotationPool getAnnotationSection() {
+ return new AnnotationPool(DexPool.this);
+ }
+
+ @Nonnull @Override public AnnotationSetPool getAnnotationSetSection() {
+ return new AnnotationSetPool(DexPool.this);
+ }
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/FieldPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/FieldPool.java
index 3403d12e..135d79bd 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/FieldPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/FieldPool.java
@@ -39,20 +39,17 @@ import javax.annotation.Nonnull;
public class FieldPool extends BaseIndexPool<FieldReference>
implements FieldSection<CharSequence, CharSequence, FieldReference, Field> {
- @Nonnull private final StringPool stringPool;
- @Nonnull private final TypePool typePool;
- public FieldPool(@Nonnull StringPool stringPool, @Nonnull TypePool typePool) {
- this.stringPool = stringPool;
- this.typePool = typePool;
+ public FieldPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
}
public void intern(@Nonnull FieldReference field) {
Integer prev = internedItems.put(field, 0);
if (prev == null) {
- typePool.intern(field.getDefiningClass());
- stringPool.intern(field.getName());
- typePool.intern(field.getType());
+ dexPool.typeSection.intern(field.getDefiningClass());
+ dexPool.stringSection.intern(field.getName());
+ dexPool.typeSection.intern(field.getType());
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/Markable.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/Markable.java
new file mode 100644
index 00000000..8b14574c
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/Markable.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.writer.pool;
+
+public interface Markable {
+ void mark();
+ void reset();
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/MethodPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/MethodPool.java
index 8103d319..2801abd0 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/MethodPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/MethodPool.java
@@ -39,26 +39,24 @@ import javax.annotation.Nonnull;
public class MethodPool extends BaseIndexPool<MethodReference>
implements MethodSection<CharSequence, CharSequence, MethodProtoReference, MethodReference, PoolMethod> {
- @Nonnull private final StringPool stringPool;
- @Nonnull private final TypePool typePool;
- @Nonnull private final ProtoPool protoPool;
- public MethodPool(@Nonnull StringPool stringPool, @Nonnull TypePool typePool,
- @Nonnull ProtoPool protoPool) {
- this.stringPool = stringPool;
- this.typePool = typePool;
- this.protoPool = protoPool;
+ public MethodPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
}
public void intern(@Nonnull MethodReference method) {
Integer prev = internedItems.put(method, 0);
if (prev == null) {
- typePool.intern(method.getDefiningClass());
- protoPool.intern(new PoolMethodProto(method));
- stringPool.intern(method.getName());
+ dexPool.typeSection.intern(method.getDefiningClass());
+ dexPool.protoSection.intern(new PoolMethodProto(method));
+ dexPool.stringSection.intern(method.getName());
}
}
+ @Nonnull @Override public MethodReference getMethodReference(@Nonnull PoolMethod poolMethod) {
+ return poolMethod;
+ }
+
@Nonnull @Override public CharSequence getDefiningClass(@Nonnull MethodReference methodReference) {
return methodReference.getDefiningClass();
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ProtoPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ProtoPool.java
index 523e5f4d..1209bd93 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ProtoPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ProtoPool.java
@@ -43,23 +43,17 @@ import java.util.List;
public class ProtoPool extends BaseIndexPool<MethodProtoReference>
implements ProtoSection<CharSequence, CharSequence, MethodProtoReference,
TypeListPool.Key<? extends Collection<? extends CharSequence>>> {
- @Nonnull private final StringPool stringPool;
- @Nonnull private final TypePool typePool;
- @Nonnull private final TypeListPool typeListPool;
- public ProtoPool(@Nonnull StringPool stringPool, @Nonnull TypePool typePool,
- @Nonnull TypeListPool typeListPool) {
- this.stringPool = stringPool;
- this.typePool = typePool;
- this.typeListPool = typeListPool;
+ public ProtoPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
}
public void intern(@Nonnull MethodProtoReference reference) {
Integer prev = internedItems.put(reference, 0);
if (prev == null) {
- stringPool.intern(getShorty(reference));
- typePool.intern(reference.getReturnType());
- typeListPool.intern(reference.getParameterTypes());
+ dexPool.stringSection.intern(getShorty(reference));
+ dexPool.typeSection.intern(reference.getReturnType());
+ dexPool.typeListSection.intern(reference.getParameterTypes());
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringPool.java
index 5886b4f6..61f1502e 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringPool.java
@@ -39,6 +39,11 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class StringPool extends StringTypeBasePool implements StringSection<CharSequence, StringReference> {
+
+ public StringPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
+ }
+
public void intern(@Nonnull CharSequence string) {
internedItems.put(string.toString(), 0);
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringTypeBasePool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringTypeBasePool.java
index 768e562d..54c6cea6 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringTypeBasePool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringTypeBasePool.java
@@ -31,7 +31,6 @@
package org.jf.dexlib2.writer.pool;
-import com.google.common.collect.Maps;
import org.jf.dexlib2.writer.DexWriter;
import org.jf.dexlib2.writer.NullableIndexSection;
import org.jf.util.ExceptionWithContext;
@@ -41,8 +40,12 @@ import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map;
-public abstract class StringTypeBasePool implements NullableIndexSection<CharSequence> {
- @Nonnull protected final Map<String, Integer> internedItems = Maps.newHashMap();
+public abstract class StringTypeBasePool extends BasePool<String, Integer>
+ implements NullableIndexSection<CharSequence>, Markable {
+
+ public StringTypeBasePool(@Nonnull DexPool dexPool) {
+ super(dexPool);
+ }
@Nonnull @Override public Collection<Map.Entry<String, Integer>> getItems() {
return internedItems.entrySet();
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypeListPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypeListPool.java
index 7e0fbe03..038f4d1a 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypeListPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypeListPool.java
@@ -43,10 +43,10 @@ import java.util.Iterator;
public class TypeListPool extends BaseNullableOffsetPool<Key<? extends Collection<? extends CharSequence>>>
implements TypeListSection<CharSequence, Key<? extends Collection<? extends CharSequence>>> {
- @Nonnull private final TypePool typePool;
- public TypeListPool(@Nonnull TypePool typePool) {
- this.typePool = typePool;
+
+ public TypeListPool(@Nonnull DexPool dexPool) {
+ super(dexPool);
}
public void intern(@Nonnull Collection<? extends CharSequence> types) {
@@ -55,7 +55,7 @@ public class TypeListPool extends BaseNullableOffsetPool<Key<? extends Collectio
Integer prev = internedItems.put(key, 0);
if (prev == null) {
for (CharSequence type: types) {
- typePool.intern(type);
+ dexPool.typeSection.intern(type);
}
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypePool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypePool.java
index 13bcd8ad..7e432089 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypePool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/TypePool.java
@@ -39,17 +39,17 @@ import javax.annotation.Nullable;
public class TypePool extends StringTypeBasePool
implements TypeSection<CharSequence, CharSequence, TypeReference> {
- @Nonnull private final StringPool stringPool;
- public TypePool(@Nonnull StringPool stringPool) {
- this.stringPool = stringPool;
+
+ public TypePool(@Nonnull DexPool dexPool) {
+ super(dexPool);
}
public void intern(@Nonnull CharSequence type) {
String typeString = type.toString();
Integer prev = internedItems.put(typeString, 0);
if (prev == null) {
- stringPool.intern(typeString);
+ dexPool.stringSection.intern(typeString);
}
}
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java b/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java
index 4c8f85bf..ff832c27 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java
@@ -79,7 +79,7 @@ public class AccessorTest {
public void testAccessors() throws IOException {
URL url = AccessorTest.class.getClassLoader().getResource("accessorTest.dex");
Assert.assertNotNull(url);
- DexFile f = DexFileFactory.loadDexFile(url.getFile(), 15, false);
+ DexFile f = DexFileFactory.loadDexFile(url.getFile(), Opcodes.getDefault());
SyntheticAccessorResolver sar = new SyntheticAccessorResolver(f.getOpcodes(), f.getClasses());
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/DexEntryFinderTest.java b/dexlib2/src/test/java/org/jf/dexlib2/DexEntryFinderTest.java
new file mode 100644
index 00000000..610d3c76
--- /dev/null
+++ b/dexlib2/src/test/java/org/jf/dexlib2/DexEntryFinderTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2;
+
+import com.beust.jcommander.internal.Maps;
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.DexFileFactory.DexEntryFinder;
+import org.jf.dexlib2.DexFileFactory.DexFileNotFoundException;
+import org.jf.dexlib2.DexFileFactory.MultipleMatchingDexEntriesException;
+import org.jf.dexlib2.DexFileFactory.UnsupportedFileTypeException;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile;
+import org.jf.dexlib2.iface.MultiDexContainer;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static org.mockito.Mockito.mock;
+
+public class DexEntryFinderTest {
+
+ @Test
+ public void testNormalStuff() throws Exception {
+ Map<String, DexBackedDexFile> entries = Maps.newHashMap();
+ DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
+ entries.put("/system/framework/framework.jar", dexFile1);
+ DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
+ entries.put("/system/framework/framework.jar:classes2.dex", dexFile2);
+ DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
+
+ Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
+
+ assertEntryNotFound(testFinder, "system/framework/framework.jar", true);
+ assertEntryNotFound(testFinder, "/framework/framework.jar", true);
+ assertEntryNotFound(testFinder, "framework/framework.jar", true);
+ assertEntryNotFound(testFinder, "/framework.jar", true);
+ assertEntryNotFound(testFinder, "framework.jar", true);
+
+ Assert.assertEquals(dexFile1, testFinder.findEntry("system/framework/framework.jar", false));
+ Assert.assertEquals(dexFile1, testFinder.findEntry("/framework/framework.jar", false));
+ Assert.assertEquals(dexFile1, testFinder.findEntry("framework/framework.jar", false));
+ Assert.assertEquals(dexFile1, testFinder.findEntry("/framework.jar", false));
+ Assert.assertEquals(dexFile1, testFinder.findEntry("framework.jar", false));
+
+ assertEntryNotFound(testFinder, "ystem/framework/framework.jar", false);
+ assertEntryNotFound(testFinder, "ssystem/framework/framework.jar", false);
+ assertEntryNotFound(testFinder, "ramework/framework.jar", false);
+ assertEntryNotFound(testFinder, "ramework.jar", false);
+ assertEntryNotFound(testFinder, "framework", false);
+
+ Assert.assertEquals(dexFile2, testFinder.findEntry("/system/framework/framework.jar:classes2.dex", true));
+
+ assertEntryNotFound(testFinder, "system/framework/framework.jar:classes2.dex", true);
+ assertEntryNotFound(testFinder, "framework.jar:classes2.dex", true);
+ assertEntryNotFound(testFinder, "classes2.dex", true);
+
+ Assert.assertEquals(dexFile2, testFinder.findEntry("system/framework/framework.jar:classes2.dex", false));
+ Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar:classes2.dex", false));
+ Assert.assertEquals(dexFile2, testFinder.findEntry("framework/framework.jar:classes2.dex", false));
+ Assert.assertEquals(dexFile2, testFinder.findEntry("/framework.jar:classes2.dex", false));
+ Assert.assertEquals(dexFile2, testFinder.findEntry("framework.jar:classes2.dex", false));
+ Assert.assertEquals(dexFile2, testFinder.findEntry(":classes2.dex", false));
+ Assert.assertEquals(dexFile2, testFinder.findEntry("classes2.dex", false));
+
+ assertEntryNotFound(testFinder, "ystem/framework/framework.jar:classes2.dex", false);
+ assertEntryNotFound(testFinder, "ramework.jar:classes2.dex", false);
+ assertEntryNotFound(testFinder, "lasses2.dex", false);
+ assertEntryNotFound(testFinder, "classes2", false);
+ }
+
+ @Test
+ public void testSimilarEntries() throws Exception {
+ Map<String, DexBackedDexFile> entries = Maps.newHashMap();
+ DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
+ entries.put("/system/framework/framework.jar", dexFile1);
+ DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
+ entries.put("system/framework/framework.jar", dexFile2);
+ DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
+
+ Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
+ Assert.assertEquals(dexFile2, testFinder.findEntry("system/framework/framework.jar", true));
+
+ assertMultipleMatchingEntries(testFinder, "/system/framework/framework.jar");
+ assertMultipleMatchingEntries(testFinder, "system/framework/framework.jar");
+
+ assertMultipleMatchingEntries(testFinder, "/framework/framework.jar");
+ assertMultipleMatchingEntries(testFinder, "framework/framework.jar");
+ assertMultipleMatchingEntries(testFinder, "/framework.jar");
+ assertMultipleMatchingEntries(testFinder, "framework.jar");
+ }
+
+ @Test
+ public void testMatchingSuffix() throws Exception {
+ Map<String, DexBackedDexFile> entries = Maps.newHashMap();
+ DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
+ entries.put("/system/framework/framework.jar", dexFile1);
+ DexBackedDexFile dexFile2 = mock(DexBackedDexFile.class);
+ entries.put("/framework/framework.jar", dexFile2);
+ DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
+
+ Assert.assertEquals(dexFile1, testFinder.findEntry("/system/framework/framework.jar", true));
+ Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar", true));
+
+ Assert.assertEquals(dexFile2, testFinder.findEntry("/framework/framework.jar", false));
+ Assert.assertEquals(dexFile2, testFinder.findEntry("framework/framework.jar", false));
+
+ assertMultipleMatchingEntries(testFinder, "/framework.jar");
+ assertMultipleMatchingEntries(testFinder, "framework.jar");
+ }
+
+ @Test
+ public void testNonDexEntries() throws Exception {
+ Map<String, DexBackedDexFile> entries = Maps.newHashMap();
+ DexBackedDexFile dexFile1 = mock(DexBackedDexFile.class);
+ entries.put("classes.dex", dexFile1);
+ entries.put("/blah/classes.dex", null);
+ DexEntryFinder testFinder = new DexEntryFinder("blah.oat", new TestMultiDexContainer(entries));
+
+ Assert.assertEquals(dexFile1, testFinder.findEntry("classes.dex", true));
+ Assert.assertEquals(dexFile1, testFinder.findEntry("classes.dex", false));
+
+ assertUnsupportedFileType(testFinder, "/blah/classes.dex", true);
+ assertDexFileNotFound(testFinder, "/blah/classes.dex", false);
+ }
+
+ private void assertEntryNotFound(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
+ try {
+ finder.findEntry(entry, exactMatch);
+ Assert.fail();
+ } catch (DexFileNotFoundException ex) {
+ // expected exception
+ }
+ }
+
+ private void assertMultipleMatchingEntries(DexEntryFinder finder, String entry) throws IOException {
+ try {
+ finder.findEntry(entry, false);
+ Assert.fail();
+ } catch (MultipleMatchingDexEntriesException ex) {
+ // expected exception
+ }
+ }
+
+ private void assertUnsupportedFileType(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
+ try {
+ finder.findEntry(entry, exactMatch);
+ Assert.fail();
+ } catch (UnsupportedFileTypeException ex) {
+ // expected exception
+ }
+ }
+
+ private void assertDexFileNotFound(DexEntryFinder finder, String entry, boolean exactMatch) throws IOException {
+ try {
+ finder.findEntry(entry, exactMatch);
+ Assert.fail();
+ } catch (DexFileNotFoundException ex) {
+ // expected exception
+ }
+ }
+
+ public static class TestMultiDexContainer implements MultiDexContainer<DexBackedDexFile> {
+ @Nonnull private final Map<String, DexBackedDexFile> entries;
+
+ public TestMultiDexContainer(@Nonnull Map<String, DexBackedDexFile> entries) {
+ this.entries = entries;
+ }
+
+ @Nonnull @Override public List<String> getDexEntryNames() throws IOException {
+ List<String> entryNames = Lists.newArrayList();
+
+ for (Entry<String, DexBackedDexFile> entry: entries.entrySet()) {
+ if (entry.getValue() != null) {
+ entryNames.add(entry.getKey());
+ }
+ }
+
+ return entryNames;
+ }
+
+ @Nullable @Override public DexBackedDexFile getEntry(@Nonnull String entryName) throws IOException {
+ if (entries.containsKey(entryName)) {
+ DexBackedDexFile entry = entries.get(entryName);
+ if (entry == null) {
+ throw new NotADexFile();
+ }
+ return entry;
+ }
+ return null;
+ }
+
+ @Nonnull @Override public Opcodes getOpcodes() {
+ return Opcodes.getDefault();
+ }
+ }
+}
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java
index 3f1ee56d..d69dd81a 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CommonSuperclassTest.java
@@ -32,8 +32,10 @@
package org.jf.dexlib2.analysis;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import junit.framework.Assert;
import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.immutable.ImmutableDexFile;
import org.junit.Test;
@@ -51,49 +53,53 @@ public class CommonSuperclassTest {
// fivetwothree
// fivethree
- private final ClassPath classPath;
+ private final ClassPath oldClassPath;
+ private final ClassPath newClassPath;
+
public CommonSuperclassTest() throws IOException {
- classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19),
- ImmutableSet.of(
- TestUtils.makeClassDef("Ljava/lang/Object;", null),
- TestUtils.makeClassDef("Ltest/one;", "Ljava/lang/Object;"),
- TestUtils.makeClassDef("Ltest/two;", "Ljava/lang/Object;"),
- TestUtils.makeClassDef("Ltest/onetwo;", "Ltest/one;"),
- TestUtils.makeClassDef("Ltest/onetwothree;", "Ltest/onetwo;"),
- TestUtils.makeClassDef("Ltest/onethree;", "Ltest/one;"),
- TestUtils.makeClassDef("Ltest/fivetwo;", "Ltest/five;"),
- TestUtils.makeClassDef("Ltest/fivetwothree;", "Ltest/fivetwo;"),
- TestUtils.makeClassDef("Ltest/fivethree;", "Ltest/five;"),
- TestUtils.makeInterfaceDef("Ljava/lang/Cloneable;"),
- TestUtils.makeInterfaceDef("Ljava/io/Serializable;"),
-
- // basic class and interface
- TestUtils.makeClassDef("Liface/classiface1;", "Ljava/lang/Object;", "Liface/iface1;"),
- TestUtils.makeInterfaceDef("Liface/iface1;"),
-
- // a more complex interface tree
- TestUtils.makeInterfaceDef("Liface/base1;"),
- // implements undefined interface
- TestUtils.makeInterfaceDef("Liface/sub1;", "Liface/base1;", "Liface/base2;"),
- // this implements sub1, so that its interfaces can't be fully resolved either
- TestUtils.makeInterfaceDef("Liface/sub2;", "Liface/base1;", "Liface/sub1;"),
- TestUtils.makeInterfaceDef("Liface/sub3;", "Liface/base1;"),
- TestUtils.makeInterfaceDef("Liface/sub4;", "Liface/base1;", "Liface/sub3;"),
- TestUtils.makeClassDef("Liface/classsub1;", "Ljava/lang/Object;", "Liface/sub1;"),
- TestUtils.makeClassDef("Liface/classsub2;", "Ljava/lang/Object;", "Liface/sub2;"),
- TestUtils.makeClassDef("Liface/classsub3;", "Ljava/lang/Object;", "Liface/sub3;",
- "Liface/base;"),
- TestUtils.makeClassDef("Liface/classsub4;", "Ljava/lang/Object;", "Liface/sub3;",
- "Liface/sub4;"),
- TestUtils.makeClassDef("Liface/classsubsub4;", "Liface/classsub4;"),
- TestUtils.makeClassDef("Liface/classsub1234;", "Ljava/lang/Object;", "Liface/sub1;",
- "Liface/sub2;", "Liface/sub3;", "Liface/sub4;")
- ))));
+ ImmutableSet<ClassDef> classes = ImmutableSet.of(
+ TestUtils.makeClassDef("Ljava/lang/Object;", null),
+ TestUtils.makeClassDef("Ltest/one;", "Ljava/lang/Object;"),
+ TestUtils.makeClassDef("Ltest/two;", "Ljava/lang/Object;"),
+ TestUtils.makeClassDef("Ltest/onetwo;", "Ltest/one;"),
+ TestUtils.makeClassDef("Ltest/onetwothree;", "Ltest/onetwo;"),
+ TestUtils.makeClassDef("Ltest/onethree;", "Ltest/one;"),
+ TestUtils.makeClassDef("Ltest/fivetwo;", "Ltest/five;"),
+ TestUtils.makeClassDef("Ltest/fivetwothree;", "Ltest/fivetwo;"),
+ TestUtils.makeClassDef("Ltest/fivethree;", "Ltest/five;"),
+ TestUtils.makeInterfaceDef("Ljava/lang/Cloneable;"),
+ TestUtils.makeInterfaceDef("Ljava/io/Serializable;"),
+
+ // basic class and interface
+ TestUtils.makeClassDef("Liface/classiface1;", "Ljava/lang/Object;", "Liface/iface1;"),
+ TestUtils.makeInterfaceDef("Liface/iface1;"),
+
+ // a more complex interface tree
+ TestUtils.makeInterfaceDef("Liface/base1;"),
+ // implements undefined interface
+ TestUtils.makeInterfaceDef("Liface/sub1;", "Liface/base1;", "Liface/base2;"),
+ // this implements sub1, so that its interfaces can't be fully resolved either
+ TestUtils.makeInterfaceDef("Liface/sub2;", "Liface/base1;", "Liface/sub1;"),
+ TestUtils.makeInterfaceDef("Liface/sub3;", "Liface/base1;"),
+ TestUtils.makeInterfaceDef("Liface/sub4;", "Liface/base1;", "Liface/sub3;"),
+ TestUtils.makeClassDef("Liface/classsub1;", "Ljava/lang/Object;", "Liface/sub1;"),
+ TestUtils.makeClassDef("Liface/classsub2;", "Ljava/lang/Object;", "Liface/sub2;"),
+ TestUtils.makeClassDef("Liface/classsub3;", "Ljava/lang/Object;", "Liface/sub3;",
+ "Liface/base;"),
+ TestUtils.makeClassDef("Liface/classsub4;", "Ljava/lang/Object;", "Liface/sub3;",
+ "Liface/sub4;"),
+ TestUtils.makeClassDef("Liface/classsubsub4;", "Liface/classsub4;"),
+ TestUtils.makeClassDef("Liface/classsub1234;", "Ljava/lang/Object;", "Liface/sub1;",
+ "Liface/sub2;", "Liface/sub3;", "Liface/sub4;"));
+
+ oldClassPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), classes)));
+ newClassPath = new ClassPath(Lists.newArrayList(new DexClassProvider(
+ new ImmutableDexFile(Opcodes.forArtVersion(72), classes))), true, 72);
}
- public void superclassTest(String commonSuperclass,
- String type1, String type2) {
+ public void superclassTest(ClassPath classPath, String commonSuperclass,
+ String type1, String type2) {
TypeProto commonSuperclassProto = classPath.getClass(commonSuperclass);
TypeProto type1Proto = classPath.getClass(type1);
TypeProto type2Proto = classPath.getClass(type2);
@@ -102,6 +108,11 @@ public class CommonSuperclassTest {
Assert.assertSame(commonSuperclassProto, type2Proto.getCommonSuperclass(type1Proto));
}
+ public void superclassTest(String commonSuperclass, String type1, String type2) {
+ superclassTest(oldClassPath, commonSuperclass, type1, type2);
+ superclassTest(newClassPath, commonSuperclass, type1, type2);
+ }
+
@Test
public void testGetCommonSuperclass() throws IOException {
String object = "Ljava/lang/Object;";
@@ -131,7 +142,11 @@ public class CommonSuperclassTest {
// same value, but different object
Assert.assertEquals(
onetwo,
- classPath.getClass(onetwo).getCommonSuperclass(new ClassProto(classPath, onetwo)).getType());
+ oldClassPath.getClass(onetwo).getCommonSuperclass(new ClassProto(oldClassPath, onetwo)).getType());
+
+ Assert.assertEquals(
+ onetwo,
+ newClassPath.getClass(onetwo).getCommonSuperclass(new ClassProto(newClassPath, onetwo)).getType());
// other object is superclass
superclassTest(object, object, one);
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java
index 90a63590..70e6a042 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java
@@ -51,11 +51,12 @@ import org.jf.dexlib2.immutable.instruction.ImmutableInstruction35mi;
import org.junit.Assert;
import org.junit.Test;
+import java.io.IOException;
import java.util.List;
public class CustomMethodInlineTableTest {
@Test
- public void testCustomMethodInlineTable_Virtual() {
+ public void testCustomMethodInlineTable_Virtual() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID));
@@ -67,10 +68,12 @@ public class CustomMethodInlineTableTest {
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, null, null, ImmutableList.of(method));
- DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
+ DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), ImmutableList.of(classDef));
+
+ ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
+ ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
+ ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
- ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
- 15, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@@ -82,7 +85,7 @@ public class CustomMethodInlineTableTest {
}
@Test
- public void testCustomMethodInlineTable_Static() {
+ public void testCustomMethodInlineTable_Static() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID));
@@ -94,10 +97,12 @@ public class CustomMethodInlineTableTest {
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, null, ImmutableList.of(method), null);
- DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
+ DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), ImmutableList.of(classDef));
+
+ ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
+ ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
+ ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
- ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
- 15, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@@ -109,7 +114,7 @@ public class CustomMethodInlineTableTest {
}
@Test
- public void testCustomMethodInlineTable_Direct() {
+ public void testCustomMethodInlineTable_Direct() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID));
@@ -121,10 +126,12 @@ public class CustomMethodInlineTableTest {
ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
null, null, null, null, ImmutableList.of(method), null);
- DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
+ DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), ImmutableList.of(classDef));
+
+ ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
+ ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
+ ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
- ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
- 15, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/MethodAnalyzerTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/MethodAnalyzerTest.java
new file mode 100644
index 00000000..215ba179
--- /dev/null
+++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/MethodAnalyzerTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.analysis;
+
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.AccessFlags;
+import org.jf.dexlib2.Opcode;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.builder.MethodImplementationBuilder;
+import org.jf.dexlib2.builder.instruction.BuilderInstruction10x;
+import org.jf.dexlib2.builder.instruction.BuilderInstruction12x;
+import org.jf.dexlib2.builder.instruction.BuilderInstruction21t;
+import org.jf.dexlib2.builder.instruction.BuilderInstruction22c;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.Method;
+import org.jf.dexlib2.iface.MethodImplementation;
+import org.jf.dexlib2.immutable.ImmutableClassDef;
+import org.jf.dexlib2.immutable.ImmutableDexFile;
+import org.jf.dexlib2.immutable.ImmutableMethod;
+import org.jf.dexlib2.immutable.ImmutableMethodParameter;
+import org.jf.dexlib2.immutable.reference.ImmutableTypeReference;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import static org.jf.dexlib2.Opcodes.forArtVersion;
+
+public class MethodAnalyzerTest {
+
+ @Test
+ public void testInstanceOfNarrowingEqz_art() throws IOException {
+ MethodImplementationBuilder builder = new MethodImplementationBuilder(2);
+
+ builder.addInstruction(new BuilderInstruction22c(Opcode.INSTANCE_OF, 0, 1,
+ new ImmutableTypeReference("Lmain;")));
+ builder.addInstruction(new BuilderInstruction21t(Opcode.IF_EQZ, 0, builder.getLabel("not_instance_of")));
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ builder.addLabel("not_instance_of");
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ MethodImplementation methodImplementation = builder.getMethodImplementation();
+
+ Method method = new ImmutableMethod("Lmain;", "narrowing",
+ Collections.singletonList(new ImmutableMethodParameter("Ljava/lang/Object;", null, null)), "V",
+ AccessFlags.PUBLIC.getValue(), null, methodImplementation);
+ ClassDef classDef = new ImmutableClassDef("Lmain;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
+ null, null, null, Collections.singletonList(method));
+ DexFile dexFile = new ImmutableDexFile(forArtVersion(56), Collections.singletonList(classDef));
+
+ ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), true, 56);
+ MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, null, false);
+
+ List<AnalyzedInstruction> analyzedInstructions = methodAnalyzer.getAnalyzedInstructions();
+ Assert.assertEquals("Lmain;", analyzedInstructions.get(2).getPreInstructionRegisterType(1).type.getType());
+
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
+ }
+
+ @Test
+ public void testInstanceOfNarrowingEqz_dalvik() throws IOException {
+ MethodImplementationBuilder builder = new MethodImplementationBuilder(2);
+
+ builder.addInstruction(new BuilderInstruction22c(Opcode.INSTANCE_OF, 0, 1,
+ new ImmutableTypeReference("Lmain;")));
+ builder.addInstruction(new BuilderInstruction21t(Opcode.IF_EQZ, 0, builder.getLabel("not_instance_of")));
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ builder.addLabel("not_instance_of");
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ MethodImplementation methodImplementation = builder.getMethodImplementation();
+
+ Method method = new ImmutableMethod("Lmain;", "narrowing",
+ Collections.singletonList(new ImmutableMethodParameter("Ljava/lang/Object;", null, null)), "V",
+ AccessFlags.PUBLIC.getValue(), null, methodImplementation);
+ ClassDef classDef = new ImmutableClassDef("Lmain;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
+ null, null, null, Collections.singletonList(method));
+ DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), Collections.singletonList(classDef));
+
+ ClassPath classPath = new ClassPath(new DexClassProvider(dexFile));
+ MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, null, false);
+
+ List<AnalyzedInstruction> analyzedInstructions = methodAnalyzer.getAnalyzedInstructions();
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(2).getPreInstructionRegisterType(1).type.getType());
+
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
+ }
+
+ @Test
+ public void testInstanceOfNarrowingNez_art() throws IOException {
+ MethodImplementationBuilder builder = new MethodImplementationBuilder(2);
+
+ builder.addInstruction(new BuilderInstruction22c(Opcode.INSTANCE_OF, 0, 1,
+ new ImmutableTypeReference("Lmain;")));
+ builder.addInstruction(new BuilderInstruction21t(Opcode.IF_NEZ, 0, builder.getLabel("instance_of")));
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ builder.addLabel("instance_of");
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ MethodImplementation methodImplementation = builder.getMethodImplementation();
+
+ Method method = new ImmutableMethod("Lmain;", "narrowing",
+ Collections.singletonList(new ImmutableMethodParameter("Ljava/lang/Object;", null, null)), "V",
+ AccessFlags.PUBLIC.getValue(), null, methodImplementation);
+ ClassDef classDef = new ImmutableClassDef("Lmain;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
+ null, null, null, Collections.singletonList(method));
+ DexFile dexFile = new ImmutableDexFile(forArtVersion(56), Collections.singletonList(classDef));
+
+ ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), true, 56);
+ MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, null, false);
+
+ List<AnalyzedInstruction> analyzedInstructions = methodAnalyzer.getAnalyzedInstructions();
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(2).getPreInstructionRegisterType(1).type.getType());
+
+ Assert.assertEquals("Lmain;", analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
+ }
+
+ @Test
+ public void testInstanceOfNarrowingNez_dalvik() throws IOException {
+ MethodImplementationBuilder builder = new MethodImplementationBuilder(2);
+
+ builder.addInstruction(new BuilderInstruction22c(Opcode.INSTANCE_OF, 0, 1,
+ new ImmutableTypeReference("Lmain;")));
+ builder.addInstruction(new BuilderInstruction21t(Opcode.IF_NEZ, 0, builder.getLabel("instance_of")));
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ builder.addLabel("instance_of");
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ MethodImplementation methodImplementation = builder.getMethodImplementation();
+
+ Method method = new ImmutableMethod("Lmain;", "narrowing",
+ Collections.singletonList(new ImmutableMethodParameter("Ljava/lang/Object;", null, null)), "V",
+ AccessFlags.PUBLIC.getValue(), null, methodImplementation);
+ ClassDef classDef = new ImmutableClassDef("Lmain;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
+ null, null, null, Collections.singletonList(method));
+ DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), Collections.singletonList(classDef));
+
+ ClassPath classPath = new ClassPath(new DexClassProvider(dexFile));
+ MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, null, false);
+
+ List<AnalyzedInstruction> analyzedInstructions = methodAnalyzer.getAnalyzedInstructions();
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(2).getPreInstructionRegisterType(1).type.getType());
+
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
+ }
+
+ @Test
+ public void testInstanceOfNarrowingAfterMove_art() throws IOException {
+ MethodImplementationBuilder builder = new MethodImplementationBuilder(3);
+
+ builder.addInstruction(new BuilderInstruction12x(Opcode.MOVE_OBJECT, 1, 2));
+ builder.addInstruction(new BuilderInstruction22c(Opcode.INSTANCE_OF, 0, 1,
+ new ImmutableTypeReference("Lmain;")));
+ builder.addInstruction(new BuilderInstruction21t(Opcode.IF_EQZ, 0, builder.getLabel("not_instance_of")));
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ builder.addLabel("not_instance_of");
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ MethodImplementation methodImplementation = builder.getMethodImplementation();
+
+ Method method = new ImmutableMethod("Lmain;", "narrowing",
+ Collections.singletonList(new ImmutableMethodParameter("Ljava/lang/Object;", null, null)), "V",
+ AccessFlags.PUBLIC.getValue(), null, methodImplementation);
+ ClassDef classDef = new ImmutableClassDef("Lmain;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
+ null, null, null, Collections.singletonList(method));
+ DexFile dexFile = new ImmutableDexFile(forArtVersion(56), Collections.singletonList(classDef));
+
+ ClassPath classPath = new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)), true, 56);
+ MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, null, false);
+
+ List<AnalyzedInstruction> analyzedInstructions = methodAnalyzer.getAnalyzedInstructions();
+ Assert.assertEquals("Lmain;", analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
+ Assert.assertEquals("Lmain;", analyzedInstructions.get(3).getPreInstructionRegisterType(2).type.getType());
+
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(4).getPreInstructionRegisterType(1).type.getType());
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(4).getPreInstructionRegisterType(2).type.getType());
+ }
+
+ @Test
+ public void testInstanceOfNarrowingAfterMove_dalvik() throws IOException {
+ MethodImplementationBuilder builder = new MethodImplementationBuilder(3);
+
+ builder.addInstruction(new BuilderInstruction12x(Opcode.MOVE_OBJECT, 1, 2));
+ builder.addInstruction(new BuilderInstruction22c(Opcode.INSTANCE_OF, 0, 1,
+ new ImmutableTypeReference("Lmain;")));
+ builder.addInstruction(new BuilderInstruction21t(Opcode.IF_EQZ, 0, builder.getLabel("not_instance_of")));
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ builder.addLabel("not_instance_of");
+ builder.addInstruction(new BuilderInstruction10x(Opcode.RETURN_VOID));
+
+ MethodImplementation methodImplementation = builder.getMethodImplementation();
+
+ Method method = new ImmutableMethod("Lmain;", "narrowing",
+ Collections.singletonList(new ImmutableMethodParameter("Ljava/lang/Object;", null, null)), "V",
+ AccessFlags.PUBLIC.getValue(), null, methodImplementation);
+ ClassDef classDef = new ImmutableClassDef("Lmain;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null,
+ null, null, null, Collections.singletonList(method));
+ DexFile dexFile = new ImmutableDexFile(Opcodes.getDefault(), Collections.singletonList(classDef));
+
+ ClassPath classPath = new ClassPath(new DexClassProvider(dexFile));
+ MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, null, false);
+
+ List<AnalyzedInstruction> analyzedInstructions = methodAnalyzer.getAnalyzedInstructions();
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(3).getPreInstructionRegisterType(2).type.getType());
+
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(4).getPreInstructionRegisterType(1).type.getType());
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(4).getPreInstructionRegisterType(2).type.getType());
+ }
+}
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java
index 84cd284b..78bc8a51 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/util/SuperclassChainTest.java
@@ -57,7 +57,7 @@ public class SuperclassChainTest {
ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(
objectClassDef, oneClassDef, twoClassDef, threeClassDef);
- ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), classes)));
+ ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), classes)));
TypeProto objectClassProto = classPath.getClass("Ljava/lang/Object;");
TypeProto oneClassProto = classPath.getClass("Ltest/one;");
@@ -88,7 +88,7 @@ public class SuperclassChainTest {
ClassDef twoClassDef = TestUtils.makeClassDef("Ltest/two;", "Ltest/one;");
ClassDef threeClassDef = TestUtils.makeClassDef("Ltest/three;", "Ltest/two;");
ImmutableSet<ClassDef> classes = ImmutableSet.<ClassDef>of(twoClassDef, threeClassDef);
- ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.forApi(19), classes)));
+ ClassPath classPath = new ClassPath(new DexClassProvider(new ImmutableDexFile(Opcodes.getDefault(), classes)));
TypeProto unknownClassProto = classPath.getUnknownClass();
TypeProto oneClassProto = classPath.getClass("Ltest/one;");
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/pool/RollbackTest.java b/dexlib2/src/test/java/org/jf/dexlib2/pool/RollbackTest.java
new file mode 100644
index 00000000..6074de14
--- /dev/null
+++ b/dexlib2/src/test/java/org/jf/dexlib2/pool/RollbackTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.dexlib2.pool;
+
+import com.google.common.collect.Lists;
+import org.jf.dexlib2.AccessFlags;
+import org.jf.dexlib2.AnnotationVisibility;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.raw.MapItem;
+import org.jf.dexlib2.dexbacked.raw.RawDexFile;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.Field;
+import org.jf.dexlib2.iface.Method;
+import org.jf.dexlib2.iface.MethodParameter;
+import org.jf.dexlib2.immutable.*;
+import org.jf.dexlib2.writer.io.MemoryDataStore;
+import org.jf.dexlib2.writer.pool.DexPool;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+public class RollbackTest {
+ @Test
+ public void testRollback() throws IOException {
+ ClassDef class1 = new ImmutableClassDef("Lcls1;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, null,
+ Lists.newArrayList(new ImmutableAnnotation(AnnotationVisibility.RUNTIME, "Lannotation;", null)),
+ Lists.<Field>newArrayList(
+ new ImmutableField("Lcls1;", "field1", "I", AccessFlags.PUBLIC.getValue(), null, null)
+ ),
+ Lists.<Method>newArrayList(
+ new ImmutableMethod("Lcls1", "method1",
+ Lists.<MethodParameter>newArrayList(new ImmutableMethodParameter("L", null, null)), "V",
+ AccessFlags.PUBLIC.getValue(), null, null))
+ );
+
+ ClassDef class2 = new ImmutableClassDef("Lcls2;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, null,
+ Lists.newArrayList(new ImmutableAnnotation(AnnotationVisibility.RUNTIME, "Lannotation2;", null)),
+ Lists.<Field>newArrayList(
+ new ImmutableField("Lcls2;", "field2", "D", AccessFlags.PUBLIC.getValue(), null, null)
+ ),
+ Lists.<Method>newArrayList(
+ new ImmutableMethod("Lcls2;", "method2",
+ Lists.<MethodParameter>newArrayList(new ImmutableMethodParameter("D", null, null)), "V",
+ AccessFlags.PUBLIC.getValue(), null, null))
+ );
+
+ RawDexFile dexFile1;
+ {
+ MemoryDataStore dataStore = new MemoryDataStore();
+ DexPool dexPool = new DexPool(Opcodes.getDefault());
+ dexPool.internClass(class1);
+ dexPool.mark();
+ dexPool.internClass(class2);
+ dexPool.reset();
+ dexPool.writeTo(dataStore);
+ dexFile1 = new RawDexFile(Opcodes.getDefault(), dataStore.getData());
+ }
+
+ RawDexFile dexFile2;
+ {
+ MemoryDataStore dataStore = new MemoryDataStore();
+ DexPool dexPool = new DexPool(Opcodes.getDefault());
+ dexPool.internClass(class1);
+ dexPool.writeTo(dataStore);
+ dexFile2 = new RawDexFile(Opcodes.getDefault(), dataStore.getData());
+ }
+
+ List<MapItem> mapItems1 = dexFile1.getMapItems();
+ List<MapItem> mapItems2 = dexFile2.getMapItems();
+ for (int i=0; i<mapItems1.size(); i++) {
+ Assert.assertEquals(mapItems1.get(i).getType(), mapItems2.get(i).getType());
+ Assert.assertEquals(mapItems1.get(i).getItemCount(), mapItems2.get(i).getItemCount());
+ }
+ }
+}
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java b/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java
index 1a0a2892..bf55e37f 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java
@@ -72,12 +72,12 @@ public class DexWriterTest {
MemoryDataStore dataStore = new MemoryDataStore();
try {
- DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(classDef)));
+ DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.getDefault(), ImmutableSet.of(classDef)));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
- DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dataStore.getData());
+ DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dataStore.getData());
ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(dbClassDef);
Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);
@@ -112,12 +112,12 @@ public class DexWriterTest {
MemoryDataStore dataStore = new MemoryDataStore();
try {
- DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.forApi(19), ImmutableSet.of(classDef)));
+ DexPool.writeTo(dataStore, new ImmutableDexFile(Opcodes.getDefault(), ImmutableSet.of(classDef)));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
- DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dataStore.getData());
+ DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dataStore.getData());
ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(dbClassDef);
Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java b/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java
index c246e0ec..340b1fa1 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java
@@ -62,7 +62,7 @@ import java.util.List;
public class JumboStringConversionTest {
@Test
public void testJumboStringConversion() throws IOException {
- DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(15));
+ DexBuilder dexBuilder = new DexBuilder(Opcodes.getDefault());
MethodImplementationBuilder methodBuilder = new MethodImplementationBuilder(1);
for (int i=0; i<66000; i++) {
@@ -92,7 +92,7 @@ public class JumboStringConversionTest {
MemoryDataStore dexStore = new MemoryDataStore();
dexBuilder.writeTo(dexStore);
- DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dexStore.getData());
+ DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexStore.getData());
ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(classDef);
@@ -122,7 +122,7 @@ public class JumboStringConversionTest {
@Test
public void testJumboStringConversion_NonMethodBuilder() throws IOException {
- DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(15));
+ DexBuilder dexBuilder = new DexBuilder(Opcodes.getDefault());
final List<Instruction> instructions = Lists.newArrayList();
for (int i=0; i<66000; i++) {
@@ -189,7 +189,7 @@ public class JumboStringConversionTest {
MemoryDataStore dexStore = new MemoryDataStore();
dexBuilder.writeTo(dexStore);
- DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(15), dexStore.getData());
+ DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexStore.getData());
ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(classDef);
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 5ccda13e..6ffa2378 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 23bc0f51..8ee9c631 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Fri Jul 08 16:46:58 PDT 2016
+#Wed Sep 28 23:22:26 AEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip
diff --git a/gradlew b/gradlew
index 9d82f789..9aa616c2 100755
--- a/gradlew
+++ b/gradlew
@@ -6,12 +6,30 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -30,6 +48,7 @@ die ( ) {
cygwin=false
msys=false
darwin=false
+nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
@@ -40,26 +59,11 @@ case "`uname`" in
MINGW* )
msys=true
;;
+ NONSTOP* )
+ nonstop=true
+ ;;
esac
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -85,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -157,4 +161,9 @@ function splitJvmOpts() {
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then
+ cd "$(dirname "$0")"
+fi
+
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 72d362da..e95643d6 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -49,7 +49,6 @@ goto fail
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
diff --git a/smali/build.gradle b/smali/build.gradle
index 318b5a97..6472f216 100644
--- a/smali/build.gradle
+++ b/smali/build.gradle
@@ -76,8 +76,8 @@ dependencies {
compile project(':util')
compile project(':dexlib2')
compile depends.antlr_runtime
+ compile depends.jcommander
compile depends.stringtemplate
- compile depends.commons_cli
testCompile depends.junit
@@ -95,7 +95,7 @@ task fatJar(type: Jar, dependsOn: jar) {
classifier = 'fat'
manifest {
- attributes('Main-Class': 'org.jf.smali.main')
+ attributes('Main-Class': 'org.jf.smali.Main')
}
doLast {
@@ -141,7 +141,8 @@ task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
dontobfuscate
dontoptimize
- keep 'public class org.jf.smali.main { public static void main(java.lang.String[]); }'
+ keep 'public class org.jf.smali.Main { public static void main(java.lang.String[]); }'
+ keep 'class com.beust.jcommander.** { *; }'
keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
dontwarn 'com.google.common.**'
diff --git a/smali/src/main/antlr/smaliParser.g b/smali/src/main/antlr/smaliParser.g
index 29cd141b..2d5eccaa 100644
--- a/smali/src/main/antlr/smaliParser.g
+++ b/smali/src/main/antlr/smaliParser.g
@@ -263,8 +263,8 @@ import org.jf.dexlib2.Opcodes;
this.allowOdex = allowOdex;
}
- public void setApiLevel(int apiLevel, boolean experimental) {
- this.opcodes = new Opcodes(apiLevel, experimental);
+ public void setApiLevel(int apiLevel) {
+ this.opcodes = Opcodes.forApi(apiLevel);
this.apiLevel = apiLevel;
}
diff --git a/smali/src/main/antlr/smaliTreeWalker.g b/smali/src/main/antlr/smaliTreeWalker.g
index d074579b..171756ec 100644
--- a/smali/src/main/antlr/smaliTreeWalker.g
+++ b/smali/src/main/antlr/smaliTreeWalker.g
@@ -85,8 +85,8 @@ import java.util.*;
this.dexBuilder = dexBuilder;
}
- public void setApiLevel(int apiLevel, boolean experimental) {
- this.opcodes = new Opcodes(apiLevel, experimental);
+ public void setApiLevel(int apiLevel) {
+ this.opcodes = Opcodes.forApi(apiLevel);
this.apiLevel = apiLevel;
}
diff --git a/smali/src/main/java/org/jf/smali/AssembleCommand.java b/smali/src/main/java/org/jf/smali/AssembleCommand.java
new file mode 100644
index 00000000..efde1825
--- /dev/null
+++ b/smali/src/main/java/org/jf/smali/AssembleCommand.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.smali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.validators.PositiveInteger;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedParameter;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.List;
+
+@Parameters(commandDescription = "Assembles smali files into a dex file.")
+@ExtendedParameters(
+ commandName = "assemble",
+ commandAliases = { "ass", "as", "a" })
+public class AssembleCommand extends Command {
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information for this command.")
+ private boolean help;
+
+ @Parameter(names = {"-j", "--jobs"},
+ description = "The number of threads to use. Defaults to the number of cores available.",
+ validateWith = PositiveInteger.class)
+ @ExtendedParameter(argumentNames = "n")
+ private int jobs = Runtime.getRuntime().availableProcessors();
+
+ @Parameter(names = {"-a", "--api"},
+ description = "The numeric api level to use while assembling.")
+ @ExtendedParameter(argumentNames = "api")
+ private int apiLevel = 15;
+
+ @Parameter(names = {"-o", "--output"},
+ description = "The name/path of the dex file to write.")
+ @ExtendedParameter(argumentNames = "file")
+ private String output = "out.dex";
+
+ @Parameter(names = "--verbose",
+ description = "Generate verbose error messages.")
+ private boolean verbose = false;
+
+ @Parameter(names = {"--allow-odex-opcodes", "--allow-odex", "--ao"},
+ description = "Allows the odex opcodes that dalvik doesn't reject to be assembled.")
+ private boolean allowOdexOpcodes;
+
+ @Parameter(description = "Assembles the given files. If a directory is specified, it will be " +
+ "recursively searched for any files with a .smali prefix")
+ @ExtendedParameter(argumentNames = "[<file>|<dir>]+")
+ private List<String> input;
+
+ public AssembleCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ @Override public void run() {
+ if (help || input == null || input.isEmpty()) {
+ usage();
+ return;
+ }
+
+ try {
+ Smali.assemble(getOptions(), input);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ protected SmaliOptions getOptions() {
+ SmaliOptions options = new SmaliOptions();
+
+ options.jobs = jobs;
+ options.apiLevel = apiLevel;
+ options.outputDexFile = output;
+ options.allowOdexOpcodes = allowOdexOpcodes;
+ options.verboseErrors = verbose;
+
+ return options;
+ }
+}
diff --git a/smali/src/main/java/org/jf/smali/HelpCommand.java b/smali/src/main/java/org/jf/smali/HelpCommand.java
new file mode 100644
index 00000000..429a7dfd
--- /dev/null
+++ b/smali/src/main/java/org/jf/smali/HelpCommand.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.smali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import org.jf.util.ConsoleUtil;
+import org.jf.util.jcommander.*;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+@Parameters(commandDescription = "Shows usage information")
+@ExtendedParameters(
+ commandName = "help",
+ commandAliases = "h")
+public class HelpCommand extends Command {
+
+ @Parameter(description = "If specified, show the detailed usage information for the given commands")
+ @ExtendedParameter(argumentNames = "commands")
+ private List<String> commands;
+
+ public HelpCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+
+ public void run() {
+ JCommander parentJc = commandAncestors.get(commandAncestors.size() - 1);
+
+ if (commands == null || commands.isEmpty()) {
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(commandAncestors));
+ } else {
+ boolean printedHelp = false;
+ for (String cmd : commands) {
+ JCommander command = ExtendedCommands.getSubcommand(parentJc, cmd);
+ if (command == null) {
+ System.err.println("No such command: " + cmd);
+ } else {
+ printedHelp = true;
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(((Command)command.getObjects().get(0)).getCommandHierarchy()));
+ }
+ }
+ if (!printedHelp) {
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(commandAncestors));
+ }
+ }
+ }
+
+ @Parameters(hidden = true)
+ @ExtendedParameters(commandName = "hlep")
+ public static class HlepCommand extends HelpCommand {
+ public HlepCommand(@Nonnull List<JCommander> commandAncestors) {
+ super(commandAncestors);
+ }
+ }
+}
diff --git a/smali/src/main/java/org/jf/smali/Main.java b/smali/src/main/java/org/jf/smali/Main.java
new file mode 100644
index 00000000..6b56fddb
--- /dev/null
+++ b/smali/src/main/java/org/jf/smali/Main.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.smali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.google.common.collect.Lists;
+import org.jf.smali.HelpCommand.HlepCommand;
+import org.jf.util.jcommander.Command;
+import org.jf.util.jcommander.ExtendedCommands;
+import org.jf.util.jcommander.ExtendedParameters;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Properties;
+
+@ExtendedParameters(
+ includeParametersInUsage = true,
+ commandName = "smali",
+ postfixDescription = "See smali help <command> for more information about a specific command")
+public class Main extends Command {
+ public static final String VERSION = loadVersion();
+
+ @Parameter(names = {"-h", "-?", "--help"}, help = true,
+ description = "Show usage information")
+ private boolean help;
+
+ @Parameter(names = {"-v", "--version"}, help = true,
+ description = "Print the version of baksmali and then exit")
+ public boolean version;
+
+ private JCommander jc;
+
+ @Override public void run() {
+ }
+
+ @Override protected JCommander getJCommander() {
+ return jc;
+ }
+
+ public Main() {
+ super(Lists.<JCommander>newArrayList());
+ }
+
+ public static void main(String[] args) {
+ Main main = new Main();
+
+ JCommander jc = new JCommander(main);
+ main.jc = jc;
+ jc.setProgramName("smali");
+ List<JCommander> commandHierarchy = main.getCommandHierarchy();
+
+ ExtendedCommands.addExtendedCommand(jc, new AssembleCommand(commandHierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new HelpCommand(commandHierarchy));
+ ExtendedCommands.addExtendedCommand(jc, new HlepCommand(commandHierarchy));
+
+ jc.parse(args);
+
+ if (main.version) {
+ version();
+ }
+
+ if (jc.getParsedCommand() == null || main.help) {
+ main.usage();
+ return;
+ }
+
+ Command command = (Command)jc.getCommands().get(jc.getParsedCommand()).getObjects().get(0);
+ command.run();
+ }
+
+ protected static void version() {
+ System.out.println("smali " + VERSION + " (http://smali.org)");
+ System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
+ System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
+ System.exit(0);
+ }
+
+ private static String loadVersion() {
+ InputStream propertiesStream = Main.class.getClassLoader().getResourceAsStream("smali.properties");
+ String version = "[unknown version]";
+ if (propertiesStream != null) {
+ Properties properties = new Properties();
+ try {
+ properties.load(propertiesStream);
+ version = properties.getProperty("application.version");
+ } catch (IOException ex) {
+ // ignore
+ }
+ }
+ return version;
+ }
+}
diff --git a/smali/src/main/java/org/jf/smali/Smali.java b/smali/src/main/java/org/jf/smali/Smali.java
new file mode 100644
index 00000000..7f3762af
--- /dev/null
+++ b/smali/src/main/java/org/jf/smali/Smali.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.smali;
+
+import com.google.common.collect.Lists;
+import org.antlr.runtime.CommonTokenStream;
+import org.antlr.runtime.Token;
+import org.antlr.runtime.TokenSource;
+import org.antlr.runtime.tree.CommonTree;
+import org.antlr.runtime.tree.CommonTreeNodeStream;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.writer.builder.DexBuilder;
+import org.jf.dexlib2.writer.io.FileDataStore;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.*;
+
+public class Smali {
+
+ /**
+ * Assemble the specified files, using the given options
+ *
+ * @param options a SmaliOptions object with the options to run smali with
+ * @param input The files/directories to process
+ * @return true if assembly completed with no errors, or false if errors were encountered
+ */
+ public static boolean assemble(final SmaliOptions options, String... input) throws IOException {
+ return assemble(options, Arrays.asList(input));
+ }
+
+ /**
+ * Assemble the specified files, using the given options
+ *
+ * @param options a SmaliOptions object with the options to run smali with
+ * @param input The files/directories to process
+ * @return true if assembly completed with no errors, or false if errors were encountered
+ */
+ public static boolean assemble(final SmaliOptions options, List<String> input) throws IOException {
+ LinkedHashSet<File> filesToProcessSet = new LinkedHashSet<File>();
+
+ for (String fileToProcess: input) {
+ File argFile = new File(fileToProcess);
+
+ if (!argFile.exists()) {
+ throw new IllegalArgumentException("Cannot find file or directory \"" + fileToProcess + "\"");
+ }
+
+ if (argFile.isDirectory()) {
+ getSmaliFilesInDir(argFile, filesToProcessSet);
+ } else if (argFile.isFile()) {
+ filesToProcessSet.add(argFile);
+ }
+ }
+
+ boolean errors = false;
+
+ final DexBuilder dexBuilder = new DexBuilder(Opcodes.forApi(options.apiLevel));
+
+ ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
+ List<Future<Boolean>> tasks = Lists.newArrayList();
+
+ for (final File file: filesToProcessSet) {
+ tasks.add(executor.submit(new Callable<Boolean>() {
+ @Override public Boolean call() throws Exception {
+ return assembleSmaliFile(file, dexBuilder, options);
+ }
+ }));
+ }
+
+ for (Future<Boolean> task: tasks) {
+ while(true) {
+ try {
+ try {
+ if (!task.get()) {
+ errors = true;
+ }
+ } catch (ExecutionException ex) {
+ throw new RuntimeException(ex);
+ }
+ } catch (InterruptedException ex) {
+ continue;
+ }
+ break;
+ }
+ }
+
+ executor.shutdown();
+
+ if (errors) {
+ return false;
+ }
+
+ dexBuilder.writeTo(new FileDataStore(new File(options.outputDexFile)));
+
+ return true;
+ }
+
+ private static void getSmaliFilesInDir(@Nonnull File dir, @Nonnull Set<File> smaliFiles) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for(File file: files) {
+ if (file.isDirectory()) {
+ getSmaliFilesInDir(file, smaliFiles);
+ } else if (file.getName().endsWith(".smali")) {
+ smaliFiles.add(file);
+ }
+ }
+ }
+ }
+
+ private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, SmaliOptions options)
+ throws Exception {
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(smaliFile);
+ InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
+
+ LexerErrorInterface lexer = new smaliFlexLexer(reader);
+ ((smaliFlexLexer)lexer).setSourceFile(smaliFile);
+ CommonTokenStream tokens = new CommonTokenStream((TokenSource)lexer);
+
+ if (options.printTokens) {
+ tokens.getTokens();
+
+ for (int i=0; i<tokens.size(); i++) {
+ Token token = tokens.get(i);
+ if (token.getChannel() == smaliParser.HIDDEN) {
+ continue;
+ }
+
+ System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
+ }
+
+ System.out.flush();
+ }
+
+ smaliParser parser = new smaliParser(tokens);
+ parser.setVerboseErrors(options.verboseErrors);
+ parser.setAllowOdex(options.allowOdexOpcodes);
+ parser.setApiLevel(options.apiLevel);
+
+ smaliParser.smali_file_return result = parser.smali_file();
+
+ if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
+ return false;
+ }
+
+ CommonTree t = result.getTree();
+
+ CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
+ treeStream.setTokenStream(tokens);
+
+ if (options.printTokens) {
+ System.out.println(t.toStringTree());
+ }
+
+ smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
+ dexGen.setApiLevel(options.apiLevel);
+
+ dexGen.setVerboseErrors(options.verboseErrors);
+ dexGen.setDexBuilder(dexBuilder);
+ dexGen.smali_file();
+
+ return dexGen.getNumberOfSyntaxErrors() == 0;
+ } finally {
+ if (fis != null) {
+ fis.close();
+ }
+ }
+ }
+}
diff --git a/smali/src/main/java/org/jf/smali/SmaliOptions.java b/smali/src/main/java/org/jf/smali/SmaliOptions.java
index 165c3a89..ac385fe6 100644
--- a/smali/src/main/java/org/jf/smali/SmaliOptions.java
+++ b/smali/src/main/java/org/jf/smali/SmaliOptions.java
@@ -36,17 +36,7 @@ public class SmaliOptions {
public String outputDexFile = "out.dex";
public int jobs = Runtime.getRuntime().availableProcessors();
- public boolean allowOdex = false;
+ public boolean allowOdexOpcodes = false;
public boolean verboseErrors = false;
public boolean printTokens = false;
- public boolean experimental = false;
-
- public boolean listMethods = false;
- public String methodListFilename = null;
-
- public boolean listFields = false;
- public String fieldListFilename = null;
-
- public boolean listTypes = false;
- public String typeListFilename = null;
}
diff --git a/smali/src/main/java/org/jf/smali/SmaliTestUtils.java b/smali/src/main/java/org/jf/smali/SmaliTestUtils.java
index bef07414..a0fe55c2 100644
--- a/smali/src/main/java/org/jf/smali/SmaliTestUtils.java
+++ b/smali/src/main/java/org/jf/smali/SmaliTestUtils.java
@@ -50,14 +50,14 @@ import java.io.StringReader;
public class SmaliTestUtils {
public static ClassDef compileSmali(String smaliText) throws RecognitionException, IOException {
- return compileSmali(smaliText, 15, false);
+ return compileSmali(smaliText, 15);
}
- public static ClassDef compileSmali(String smaliText, int apiLevel, boolean experimental)
+ public static ClassDef compileSmali(String smaliText, int apiLevel)
throws RecognitionException, IOException {
CommonTokenStream tokens;
LexerErrorInterface lexer;
- DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(apiLevel, experimental));
+ DexBuilder dexBuilder = new DexBuilder(Opcodes.forApi(apiLevel));
Reader reader = new StringReader(smaliText);
@@ -67,7 +67,7 @@ public class SmaliTestUtils {
smaliParser parser = new smaliParser(tokens);
parser.setVerboseErrors(true);
parser.setAllowOdex(false);
- parser.setApiLevel(apiLevel, experimental);
+ parser.setApiLevel(apiLevel);
smaliParser.smali_file_return result = parser.smali_file();
@@ -81,7 +81,7 @@ public class SmaliTestUtils {
treeStream.setTokenStream(tokens);
smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
- dexGen.setApiLevel(apiLevel, experimental);
+ dexGen.setApiLevel(apiLevel);
dexGen.setVerboseErrors(true);
dexGen.setDexBuilder(dexBuilder);
dexGen.smali_file();
@@ -94,7 +94,7 @@ public class SmaliTestUtils {
dexBuilder.writeTo(dataStore);
- DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(apiLevel, experimental), dataStore.getData());
+ DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.forApi(apiLevel), dataStore.getData());
return Iterables.getFirst(dexFile.getClasses(), null);
}
diff --git a/smali/src/main/java/org/jf/smali/main.java b/smali/src/main/java/org/jf/smali/main.java
deleted file mode 100644
index e5562808..00000000
--- a/smali/src/main/java/org/jf/smali/main.java
+++ /dev/null
@@ -1,491 +0,0 @@
-/*
- * [The "BSD licence"]
- * Copyright (c) 2010 Ben Gruver (JesusFreke)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.jf.smali;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-import org.antlr.runtime.CommonTokenStream;
-import org.antlr.runtime.Token;
-import org.antlr.runtime.TokenSource;
-import org.antlr.runtime.tree.CommonTree;
-import org.antlr.runtime.tree.CommonTreeNodeStream;
-import org.apache.commons.cli.*;
-import org.jf.dexlib2.Opcodes;
-import org.jf.dexlib2.writer.builder.DexBuilder;
-import org.jf.dexlib2.writer.io.FileDataStore;
-import org.jf.util.ConsoleUtil;
-import org.jf.util.SmaliHelpFormatter;
-
-import javax.annotation.Nonnull;
-import java.io.*;
-import java.util.*;
-import java.util.concurrent.*;
-
-/**
- * Main class for smali. It recognizes enough options to be able to dispatch
- * to the right "actual" main.
- */
-public class main {
-
- public static final String VERSION;
-
- private final static Options basicOptions;
- private final static Options debugOptions;
- private final static Options options;
-
- static {
- basicOptions = new Options();
- debugOptions = new Options();
- options = new Options();
- buildOptions();
-
- InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties");
- if (templateStream != null) {
- Properties properties = new Properties();
- String version = "(unknown)";
- try {
- properties.load(templateStream);
- version = properties.getProperty("application.version");
- } catch (IOException ex) {
- // just eat it
- }
- VERSION = version;
- } else {
- VERSION = "[unknown version]";
- }
- }
-
-
- /**
- * This class is uninstantiable.
- */
- private main() {
- }
-
- /**
- * A more programmatic-friendly entry point for smali
- *
- * @param options a SmaliOptions object with the options to run smali with
- * @param input The files/directories to process
- * @return true if assembly completed with no errors, or false if errors were encountered
- */
- public static boolean run(final SmaliOptions options, String... input) throws IOException {
- LinkedHashSet<File> filesToProcessSet = new LinkedHashSet<File>();
-
- for (String fileToProcess: input) {
- File argFile = new File(fileToProcess);
-
- if (!argFile.exists()) {
- throw new IllegalArgumentException("Cannot find file or directory \"" + fileToProcess + "\"");
- }
-
- if (argFile.isDirectory()) {
- getSmaliFilesInDir(argFile, filesToProcessSet);
- } else if (argFile.isFile()) {
- filesToProcessSet.add(argFile);
- }
- }
-
- boolean errors = false;
-
- final DexBuilder dexBuilder = DexBuilder.makeDexBuilder(
- Opcodes.forApi(options.apiLevel, options.experimental));
-
- ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
- List<Future<Boolean>> tasks = Lists.newArrayList();
-
- for (final File file: filesToProcessSet) {
- tasks.add(executor.submit(new Callable<Boolean>() {
- @Override public Boolean call() throws Exception {
- return assembleSmaliFile(file, dexBuilder, options);
- }
- }));
- }
-
- for (Future<Boolean> task: tasks) {
- while(true) {
- try {
- try {
- if (!task.get()) {
- errors = true;
- }
- } catch (ExecutionException ex) {
- throw new RuntimeException(ex);
- }
- } catch (InterruptedException ex) {
- continue;
- }
- break;
- }
- }
-
- executor.shutdown();
-
- if (errors) {
- return false;
- }
-
- if (options.listMethods) {
- if (Strings.isNullOrEmpty(options.methodListFilename)) {
- options.methodListFilename = options.outputDexFile + ".methods";
- }
- writeReferences(dexBuilder.getMethodReferences(), options.methodListFilename);
- }
-
- if (options.listFields) {
- if (Strings.isNullOrEmpty(options.fieldListFilename)) {
- options.fieldListFilename = options.outputDexFile + ".fields";
- }
- writeReferences(dexBuilder.getFieldReferences(), options.fieldListFilename);
- }
-
- if (options.listTypes) {
- if (Strings.isNullOrEmpty(options.typeListFilename)) {
- options.typeListFilename = options.outputDexFile + ".types";
- }
- writeReferences(dexBuilder.getTypeReferences(), options.typeListFilename);
- }
-
- dexBuilder.writeTo(new FileDataStore(new File(options.outputDexFile)));
-
- return true;
- }
-
- /**
- * Run!
- */
- public static void main(String[] args) {
- Locale locale = new Locale("en", "US");
- Locale.setDefault(locale);
-
- CommandLineParser parser = new PosixParser();
- CommandLine commandLine;
-
- try {
- commandLine = parser.parse(options, args);
- } catch (ParseException ex) {
- usage();
- return;
- }
-
- SmaliOptions smaliOptions = new SmaliOptions();
-
- String[] remainingArgs = commandLine.getArgs();
-
- Option[] options = commandLine.getOptions();
-
- for (int i=0; i<options.length; i++) {
- Option option = options[i];
- String opt = option.getOpt();
-
- switch (opt.charAt(0)) {
- case 'v':
- version();
- return;
- case '?':
- while (++i < options.length) {
- if (options[i].getOpt().charAt(0) == '?') {
- usage(true);
- return;
- }
- }
- usage(false);
- return;
- case 'o':
- smaliOptions.outputDexFile = commandLine.getOptionValue("o");
- break;
- case 'x':
- smaliOptions.allowOdex = true;
- break;
- case 'X':
- smaliOptions.experimental = true;
- break;
- case 'a':
- smaliOptions.apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
- break;
- case 'j':
- smaliOptions.jobs = Integer.parseInt(commandLine.getOptionValue("j"));
- break;
- case 'm':
- smaliOptions.listMethods = true;
- smaliOptions.methodListFilename = commandLine.getOptionValue("m");
- break;
- case 'f':
- smaliOptions.listFields = true;
- smaliOptions.fieldListFilename = commandLine.getOptionValue("f");
- break;
- case 't':
- smaliOptions.listTypes = true;
- smaliOptions.typeListFilename = commandLine.getOptionValue("t");
- break;
- case 'V':
- smaliOptions.verboseErrors = true;
- break;
- case 'T':
- smaliOptions.printTokens = true;
- break;
- default:
- assert false;
- }
- }
-
- if (remainingArgs.length == 0) {
- usage();
- return;
- }
-
- try {
- if (!run(smaliOptions, remainingArgs)) {
- System.exit(1);
- }
- } catch (RuntimeException ex) {
- System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
- ex.printStackTrace();
- System.exit(2);
- } catch (Throwable ex) {
- System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:");
- ex.printStackTrace();
- System.exit(3);
- }
- }
-
- private static void writeReferences(List<String> references, String filename) {
- PrintWriter writer = null;
- try {
- writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)));
-
- for (String reference: Ordering.natural().sortedCopy(references)) {
- writer.println(reference);
- }
- } catch (IOException ex) {
- throw new RuntimeException(ex);
- } finally {
- if (writer != null) {
- writer.close();
- }
- }
- }
-
- private static void getSmaliFilesInDir(@Nonnull File dir, @Nonnull Set<File> smaliFiles) {
- File[] files = dir.listFiles();
- if (files != null) {
- for(File file: files) {
- if (file.isDirectory()) {
- getSmaliFilesInDir(file, smaliFiles);
- } else if (file.getName().endsWith(".smali")) {
- smaliFiles.add(file);
- }
- }
- }
- }
-
- private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, SmaliOptions options)
- throws Exception {
- CommonTokenStream tokens;
-
- LexerErrorInterface lexer;
-
- FileInputStream fis = new FileInputStream(smaliFile);
- InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
-
- lexer = new smaliFlexLexer(reader);
- ((smaliFlexLexer)lexer).setSourceFile(smaliFile);
- tokens = new CommonTokenStream((TokenSource)lexer);
-
- if (options.printTokens) {
- tokens.getTokens();
-
- for (int i=0; i<tokens.size(); i++) {
- Token token = tokens.get(i);
- if (token.getChannel() == smaliParser.HIDDEN) {
- continue;
- }
-
- System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
- }
-
- System.out.flush();
- }
-
- smaliParser parser = new smaliParser(tokens);
- parser.setVerboseErrors(options.verboseErrors);
- parser.setAllowOdex(options.allowOdex);
- parser.setApiLevel(options.apiLevel, options.experimental);
-
- smaliParser.smali_file_return result = parser.smali_file();
-
- if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
- return false;
- }
-
- CommonTree t = result.getTree();
-
- CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
- treeStream.setTokenStream(tokens);
-
- if (options.printTokens) {
- System.out.println(t.toStringTree());
- }
-
- smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
- dexGen.setApiLevel(options.apiLevel, options.experimental);
-
- dexGen.setVerboseErrors(options.verboseErrors);
- dexGen.setDexBuilder(dexBuilder);
- dexGen.smali_file();
-
- return dexGen.getNumberOfSyntaxErrors() == 0;
- }
-
-
- /**
- * Prints the usage message.
- */
- private static void usage(boolean printDebugOptions) {
- SmaliHelpFormatter formatter = new SmaliHelpFormatter();
-
- int consoleWidth = ConsoleUtil.getConsoleWidth();
- if (consoleWidth <= 0) {
- consoleWidth = 80;
- }
-
- formatter.setWidth(consoleWidth);
-
- formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*",
- "assembles a set of smali files into a dex file", basicOptions, printDebugOptions?debugOptions:null);
- }
-
- private static void usage() {
- usage(false);
- }
-
- /**
- * Prints the version message.
- */
- private static void version() {
- System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
- System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
- System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
- System.exit(0);
- }
-
- @SuppressWarnings("AccessStaticViaInstance")
- private static void buildOptions() {
- Option versionOption = OptionBuilder.withLongOpt("version")
- .withDescription("prints the version then exits")
- .create("v");
-
- Option helpOption = OptionBuilder.withLongOpt("help")
- .withDescription("prints the help message then exits. Specify twice for debug options")
- .create("?");
-
- Option outputOption = OptionBuilder.withLongOpt("output")
- .withDescription("the name of the dex file that will be written. The default is out.dex")
- .hasArg()
- .withArgName("FILE")
- .create("o");
-
- Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions")
- .withDescription("allow odex instructions to be compiled into the dex file. Only a few" +
- " instructions are supported - the ones that can exist in a dead code path and not" +
- " cause dalvik to reject the class")
- .create("x");
-
- Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
- .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " +
- "specified, it defaults to 15 (ICS).")
- .hasArg()
- .withArgName("API_LEVEL")
- .create("a");
-
- Option listMethodsOption = OptionBuilder.withLongOpt("list-methods")
- .withDescription("Lists all the method references to FILE" +
- " (<output_dex_filename>.methods by default)")
- .hasOptionalArg()
- .withArgName("FILE")
- .create("m");
-
- Option listFieldsOption = OptionBuilder.withLongOpt("list-fields")
- .withDescription("Lists all the field references to FILE" +
- " (<output_dex_filename>.fields by default)")
- .hasOptionalArg()
- .withArgName("FILE")
- .create("f");
-
- Option listClassesOption = OptionBuilder.withLongOpt("list-types")
- .withDescription("Lists all the type references to FILE" +
- " (<output_dex_filename>.types by default)")
- .hasOptionalArg()
- .withArgName("FILE")
- .create("t");
-
- Option experimentalOption = OptionBuilder.withLongOpt("experimental")
- .withDescription("enable experimental opcodes to be assembled, even if they " +
- " aren't necessarily supported by the Android runtime yet")
- .create("X");
-
- Option jobsOption = OptionBuilder.withLongOpt("jobs")
- .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " +
- "maximum of 6")
- .hasArg()
- .withArgName("NUM_THREADS")
- .create("j");
-
- Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors")
- .withDescription("Generate verbose error messages")
- .create("V");
-
- Option printTokensOption = OptionBuilder.withLongOpt("print-tokens")
- .withDescription("Print the name and text of each token")
- .create("T");
-
- basicOptions.addOption(versionOption);
- basicOptions.addOption(helpOption);
- basicOptions.addOption(outputOption);
- basicOptions.addOption(allowOdexOption);
- basicOptions.addOption(apiLevelOption);
- basicOptions.addOption(experimentalOption);
- basicOptions.addOption(jobsOption);
- basicOptions.addOption(listMethodsOption);
- basicOptions.addOption(listFieldsOption);
- basicOptions.addOption(listClassesOption);
-
- debugOptions.addOption(verboseErrorsOption);
- debugOptions.addOption(printTokensOption);
-
- for (Object option: basicOptions.getOptions()) {
- options.addOption((Option)option);
- }
-
- for (Object option: debugOptions.getOptions()) {
- options.addOption((Option)option);
- }
- }
-} \ No newline at end of file
diff --git a/smalidea/build.gradle b/smalidea/build.gradle
index 8cba19d2..57209762 100644
--- a/smalidea/build.gradle
+++ b/smalidea/build.gradle
@@ -34,9 +34,13 @@ buildscript {
maven {
url "https://plugins.gradle.org/m2/"
}
+ maven {
+ url 'http://dl.bintray.com/jetbrains/intellij-plugin-service'
+ }
+
}
dependencies {
- classpath 'gradle.plugin.org.jetbrains:gradle-intellij-plugin:0.0.40'
+ classpath "gradle.plugin.org.jetbrains:gradle-intellij-plugin:0.1.10"
}
}
@@ -44,7 +48,8 @@ apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'antlr'
-version = '0.03'
+
+version = '0.05'
if (!('release' in gradle.startParameter.taskNames)) {
def versionSuffix
@@ -86,14 +91,12 @@ if (System.env.JDK7_HOME != null) {
def sandboxDir = "${buildDir}/sandbox"
-// We don't want to use the org.jetbrains.intellij plugin when generating the idea project files,
-// so that idea classes aren't included as project dependencies, since they will already exist
-// in the plugin sdk defined for the project
if (!('idea' in gradle.startParameter.taskNames)) {
+
apply plugin: 'org.jetbrains.intellij'
intellij {
- version 'IC-15.0.6'
+ version 'IC-2016.3.5'
pluginName 'smalidea'
updateSinceUntilBuild false
@@ -106,8 +109,8 @@ if (!('idea' in gradle.startParameter.taskNames)) {
task ideaDirs() {
project.afterEvaluate {
if (intellij != null) {
- println "IDEA Plugin jdk: ${intellij.ideaDirectory}"
- println "sources: ${project.configurations['intellij-sources'].files[0]}"
+ println "IDEA Plugin jdk: ${intellij.ideaDependency.classes}"
+ println "sources: ${intellij.ideaDependency.getSources()}"
}
}
}
@@ -115,13 +118,8 @@ if (!('idea' in gradle.startParameter.taskNames)) {
dependencies {
compile files("${System.properties['java.home']}/../lib/tools.jar")
}
-} else {
- // If we're running the idea task, let's make sure nothing else is being run, since
- // we have to use a special configuration for the idea task
- if (gradle.startParameter.taskNames.size() > 1) {
- throw new InvalidUserDataException("The idea task must be run by itself.")
- }
+} else {
project(':') {
idea {
project {
@@ -209,13 +207,15 @@ dependencies {
}
task extractTokens(type: org.gradle.api.tasks.Copy, dependsOn: ':smali:build') {
- def allArtifacts = configurations.default.resolvedConfiguration.resolvedArtifacts
- def smaliArtifact = allArtifacts.find { it.moduleVersion.id.name.equals('smali') }
+ project.afterEvaluate {
+ def allArtifacts = configurations.default.resolvedConfiguration.resolvedArtifacts
+ def smaliArtifact = allArtifacts.find { it.moduleVersion.id.name.equals('smali') }
- from(zipTree(smaliArtifact.file)) {
- include '**/*.tokens'
+ from(zipTree(smaliArtifact.file)) {
+ include '**/*.tokens'
+ }
+ into "${buildDir}/tokens"
}
- into "${buildDir}/tokens"
}
generateGrammarSource {
@@ -225,7 +225,6 @@ generateGrammarSource {
outputDirectory(file("${buildDir}/generated-src/antlr/main/org/jf/smalidea"))
}
generateGrammarSource.dependsOn(extractTokens)
-
ideaModule.dependsOn(generateGrammarSource)
task release(dependsOn: 'buildPlugin') {
diff --git a/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java b/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java
index 5e2dd0c9..82a190b7 100644
--- a/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java
+++ b/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliCodeFragmentFactory.java
@@ -61,6 +61,7 @@ import org.jf.smalidea.psi.impl.SmaliMethod;
import org.jf.smalidea.util.NameUtils;
import org.jf.smalidea.util.PsiUtil;
+import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
@@ -274,9 +275,14 @@ public class SmaliCodeFragmentFactory extends DefaultCodeFragmentFactory {
return originalContext;
}
+ @Nullable
public static Value evaluateRegister(EvaluationContext context, final SmaliMethod smaliMethod,
final int registerNum, final String type) throws EvaluateException {
+ if (registerNum >= smaliMethod.getRegisterCount()) {
+ return null;
+ }
+
final StackFrameProxy frameProxy = context.getSuspendContext().getFrameProxy();
if (frameProxy == null) {
return null;
@@ -304,12 +310,21 @@ public class SmaliCodeFragmentFactory extends DefaultCodeFragmentFactory {
for (SmaliInstruction instruction: smaliMethod.getInstructions()) {
methodSize += instruction.getInstructionSize();
}
- Location endLocation = method.locationOfCodeIndex((methodSize/2) - 1);
+ Location endLocation = null;
+ for (int endCodeIndex = (methodSize/2) - 1; endCodeIndex >= 0; endCodeIndex--) {
+ endLocation = method.locationOfCodeIndex(endCodeIndex);
+ if (endLocation != null) {
+ break;
+ }
+ }
+ if (endLocation == null) {
+ return null;
+ }
LocalVariable localVariable = localVariableConstructor.newInstance(vm,
method,
mapRegister(frameProxy.getStackFrame().virtualMachine(), smaliMethod, registerNum),
- method.locationOfCodeIndex(0),
+ method.location(),
endLocation,
String.format("v%d", registerNum), type, null);
diff --git a/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliPositionManager.java b/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliPositionManager.java
index 781a8569..9c0abbe6 100644
--- a/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliPositionManager.java
+++ b/smalidea/src/main/java/org/jf/smalidea/debugging/SmaliPositionManager.java
@@ -38,6 +38,7 @@ import com.intellij.debugger.engine.DebugProcess;
import com.intellij.debugger.requests.ClassPrepareRequestor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Computable;
+import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
@@ -60,10 +61,16 @@ public class SmaliPositionManager implements PositionManager {
this.debugProcess = debugProcess;
}
- public SourcePosition getSourcePosition(String declaringType, String methodName, String methodSignature,
+ public SourcePosition getSourcePosition(final String declaringType, String methodName, String methodSignature,
int codeIndex) throws NoDataException {
- Collection<SmaliClass> classes = SmaliClassNameIndex.INSTANCE.get(declaringType,
- debugProcess.getProject(), GlobalSearchScope.projectScope(debugProcess.getProject()));
+
+ Collection<SmaliClass> classes = ApplicationManager.getApplication().runReadAction(
+ new Computable<Collection<SmaliClass>>() {
+ @Override public Collection<SmaliClass> compute() {
+ return SmaliClassNameIndex.INSTANCE.get(declaringType, debugProcess.getProject(),
+ GlobalSearchScope.projectScope(debugProcess.getProject()));
+ }
+ });
if (classes.size() > 0) {
SmaliClass smaliClass = classes.iterator().next();
@@ -116,7 +123,13 @@ public class SmaliPositionManager implements PositionManager {
@Override @NotNull
public List<Location> locationsOfLine(@NotNull final ReferenceType type,
@NotNull final SourcePosition position) throws NoDataException {
- if (!(position.getElementAt().getContainingFile() instanceof SmaliFile)) {
+ PsiFile containingFile = ApplicationManager.getApplication().runReadAction(new Computable<PsiFile>() {
+ @Override public PsiFile compute() {
+ return position.getElementAt().getContainingFile();
+ }
+ });
+
+ if (!(containingFile instanceof SmaliFile)) {
throw NoDataException.INSTANCE;
}
@@ -125,6 +138,8 @@ public class SmaliPositionManager implements PositionManager {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
+
+
String typeName = type.name();
Collection<SmaliClass> classes = SmaliClassNameIndex.INSTANCE.get(typeName, debugProcess.getProject(),
GlobalSearchScope.projectScope(debugProcess.getProject()));
diff --git a/smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyStringReference.java b/smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyStringReference.java
index 88fd0070..2b52e2b7 100644
--- a/smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyStringReference.java
+++ b/smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyStringReference.java
@@ -32,6 +32,8 @@
package org.jf.smalidea.debugging.value;
import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiSubstitutor;
+import com.sun.jdi.ObjectReference;
import com.sun.jdi.StringReference;
import org.jf.smalidea.psi.impl.SmaliMethod;
@@ -41,6 +43,11 @@ public class LazyStringReference extends LazyObjectReference<StringReference> im
}
public String value() {
+ ObjectReference objectReference = getValue();
+ if (!(objectReference instanceof StringReference)) {
+ throw new IllegalStateException(String.format("Expecting type String, but got %s. method=%s, register=%d",
+ objectReference.type().name(), this.method.getSignature(PsiSubstitutor.EMPTY), registerNumber));
+ }
return getValue().value();
}
}
diff --git a/smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyValue.java b/smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyValue.java
index 0eeb010b..f17df6d7 100644
--- a/smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyValue.java
+++ b/smalidea/src/main/java/org/jf/smalidea/debugging/value/LazyValue.java
@@ -48,10 +48,10 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class LazyValue<T extends Value> implements Value {
- private final int registerNumber;
- private final Project project;
- private final SmaliMethod method;
- private final String type;
+ protected final int registerNumber;
+ protected final Project project;
+ protected final SmaliMethod method;
+ protected final String type;
private EvaluationContext evaluationContext;
private Value value;
diff --git a/smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaPackedSwitchPayload.java b/smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaPackedSwitchPayload.java
index 9d2a0fc3..6056da37 100644
--- a/smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaPackedSwitchPayload.java
+++ b/smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaPackedSwitchPayload.java
@@ -86,7 +86,7 @@ public class SmalideaPackedSwitchPayload extends SmalideaInstruction implements
return 0;
}
- return label.getOffset() - baseOffset;
+ return (label.getOffset() - baseOffset) / 2;
}
});
}
diff --git a/smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaSparseSwitchPayload.java b/smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaSparseSwitchPayload.java
index 15eaea2d..832c8807 100644
--- a/smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaSparseSwitchPayload.java
+++ b/smalidea/src/main/java/org/jf/smalidea/dexlib/instruction/SmalideaSparseSwitchPayload.java
@@ -79,7 +79,7 @@ public class SmalideaSparseSwitchPayload extends SmalideaInstruction implements
return 0;
}
- return label.getOffset() - baseOffset;
+ return (label.getOffset() - baseOffset) / 2;
}
};
}
diff --git a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliAnnotation.java b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliAnnotation.java
index e36313ba..ac3dd81f 100644
--- a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliAnnotation.java
+++ b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliAnnotation.java
@@ -106,7 +106,7 @@ public class SmaliAnnotation extends SmaliStubBasedPsiElement<SmaliAnnotationStu
}
@Nullable @Override public PsiAnnotationOwner getOwner() {
- return (PsiAnnotationOwner)getStubOrPsiParent();
+ return (PsiAnnotationOwner)getParentByStub();
}
@Nullable @Override public PsiMetaData getMetaData() {
diff --git a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliField.java b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliField.java
index 7bef4e94..579401cf 100644
--- a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliField.java
+++ b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliField.java
@@ -88,7 +88,7 @@ public class SmaliField extends SmaliStubBasedPsiElement<SmaliFieldStub> impleme
}
@Nullable @Override public PsiClass getContainingClass() {
- return (PsiClass)getStubOrPsiParent();
+ return (PsiClass)getParentByStub();
}
@NotNull @Override public PsiType getType() {
diff --git a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliImplementsList.java b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliImplementsList.java
index 8992ab0b..fb4a788f 100644
--- a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliImplementsList.java
+++ b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliImplementsList.java
@@ -56,7 +56,7 @@ public class SmaliImplementsList extends SmaliBaseReferenceList<SmaliImplementsL
}
@NotNull private SmaliClassTypeElement[] getImplementsElements() {
- SmaliClass smaliClass = (SmaliClass)getStubOrPsiParent();
+ SmaliClass smaliClass = (SmaliClass)getParentByStub();
assert smaliClass != null;
SmaliImplementsStatement[] implementsStatements = smaliClass.getImplementsStatements();
diff --git a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliInstruction.java b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliInstruction.java
index 8cb2d77a..ecbdbb33 100644
--- a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliInstruction.java
+++ b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliInstruction.java
@@ -76,7 +76,7 @@ public class SmaliInstruction extends SmaliCompositeElement {
assert instructionNode != null;
// TODO: put a project level Opcodes instance with the appropriate api level somewhere
- opcode = new Opcodes(15, false).getOpcodeByName(instructionNode.getText());
+ opcode = Opcodes.getDefault().getOpcodeByName(instructionNode.getText());
if (opcode == null) {
if (instructionNode.getText().equals(".packed-switch")) {
return Opcode.PACKED_SWITCH_PAYLOAD;
diff --git a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliMethod.java b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliMethod.java
index 085585bb..8ba618b3 100644
--- a/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliMethod.java
+++ b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliMethod.java
@@ -259,7 +259,7 @@ public class SmaliMethod extends SmaliStubBasedPsiElement<SmaliMethodStub>
}
@Nullable @Override public SmaliClass getContainingClass() {
- PsiElement parent = getStubOrPsiParent();
+ PsiElement parent = getParentByStub();
if (parent instanceof SmaliClass) {
return (SmaliClass) parent;
}
diff --git a/smalidea/src/main/java/org/jf/smalidea/psi/index/SmaliClassFinder.java b/smalidea/src/main/java/org/jf/smalidea/psi/index/SmaliClassFinder.java
index 0ebb1eec..87b2afff 100644
--- a/smalidea/src/main/java/org/jf/smalidea/psi/index/SmaliClassFinder.java
+++ b/smalidea/src/main/java/org/jf/smalidea/psi/index/SmaliClassFinder.java
@@ -31,6 +31,7 @@
package org.jf.smalidea.psi.index;
+import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElementFinder;
import com.intellij.psi.search.GlobalSearchScope;
@@ -40,9 +41,16 @@ import org.jf.smalidea.psi.impl.SmaliClass;
import java.util.Collection;
public class SmaliClassFinder extends PsiElementFinder {
+
+ private final Project project;
+
+ public SmaliClassFinder(Project project) {
+ this.project = project;
+ }
+
@Override
public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
- Collection<SmaliClass> classes = SmaliClassNameIndex.INSTANCE.get(qualifiedName, scope.getProject(), scope);
+ Collection<SmaliClass> classes = SmaliClassNameIndex.INSTANCE.get(qualifiedName, project, scope);
if (classes != null && classes.size() == 1) {
return classes.iterator().next();
}
diff --git a/smalidea/src/main/resources/META-INF/plugin.xml b/smalidea/src/main/resources/META-INF/plugin.xml
index 8ae92dad..91326cfe 100644
--- a/smalidea/src/main/resources/META-INF/plugin.xml
+++ b/smalidea/src/main/resources/META-INF/plugin.xml
@@ -1,7 +1,7 @@
<idea-plugin version="2">
<id>org.jf.smalidea</id>
<name>Smalidea</name>
- <version>0.02</version>
+ <version>0.04</version>
<vendor email="jesusfreke@jesusfreke.com" url="http://smali.org">JesusFreke</vendor>
<description><![CDATA[
diff --git a/util/build.gradle b/util/build.gradle
index 407ef71f..23d6a3af 100644
--- a/util/build.gradle
+++ b/util/build.gradle
@@ -30,9 +30,9 @@
*/
dependencies {
- compile depends.commons_cli
compile depends.findbugs
compile depends.guava
+ compile depends.jcommander
testCompile depends.junit
}
diff --git a/util/src/main/java/org/jf/util/OldWrappedIndentingWriter.java b/util/src/main/java/org/jf/util/OldWrappedIndentingWriter.java
new file mode 100644
index 00000000..f4577179
--- /dev/null
+++ b/util/src/main/java/org/jf/util/OldWrappedIndentingWriter.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2013, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.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 OldWrappedIndentingWriter extends FilterWriter {
+ /** null-ok; optional prefix for every line */
+ private final String prefix;
+
+ /** &gt; 0; the maximum output width */
+ private final int width;
+
+ /** &gt; 0; the maximum indent */
+ private final int maxIndent;
+
+ /** &gt;= 0; current output column (zero-based) */
+ private int column;
+
+ /** whether indent spaces are currently being collected */
+ private boolean collectingIndent;
+
+ /** &gt;= 0; current indent amount */
+ private int indent;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param out non-null; writer to send final output to
+ * @param width &gt;= 0; the maximum output width (not including
+ * <code>prefix</code>), or <code>0</code> for no maximum
+ * @param prefix non-null; the prefix for each line
+ */
+ public OldWrappedIndentingWriter(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 non-null; writer to send final output to
+ * @param width &gt;= 0; the maximum output width (not including
+ * <code>prefix</code>), or <code>0</code> for no maximum
+ */
+ public OldWrappedIndentingWriter(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/util/src/main/java/org/jf/util/PathUtil.java b/util/src/main/java/org/jf/util/PathUtil.java
index 91eb7584..9ba9f301 100644
--- a/util/src/main/java/org/jf/util/PathUtil.java
+++ b/util/src/main/java/org/jf/util/PathUtil.java
@@ -28,9 +28,12 @@
package org.jf.util;
+import com.google.common.collect.Lists;
+
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.List;
public class PathUtil {
private PathUtil() {
@@ -44,19 +47,9 @@ public class PathUtil {
return new File(getRelativeFileInternal(baseFile.getCanonicalFile(), fileToRelativize.getCanonicalFile()));
}
- public static String getRelativePath(String basePath, String pathToRelativize) throws IOException {
- File baseFile = new File(basePath);
- if (baseFile.isFile()) {
- baseFile = baseFile.getParentFile();
- }
-
- return getRelativeFileInternal(baseFile.getCanonicalFile(),
- new File(pathToRelativize).getCanonicalFile());
- }
-
static String getRelativeFileInternal(File canonicalBaseFile, File canonicalFileToRelativize) {
- ArrayList<String> basePath = getPathComponents(canonicalBaseFile);
- ArrayList<String> pathToRelativize = getPathComponents(canonicalFileToRelativize);
+ List<String> basePath = getPathComponents(canonicalBaseFile);
+ List<String> pathToRelativize = getPathComponents(canonicalFileToRelativize);
//if the roots aren't the same (i.e. different drives on a windows machine), we can't construct a relative
//path from one to the other, so just return the canonical file
@@ -105,21 +98,21 @@ public class PathUtil {
return sb.toString();
}
- private static ArrayList<String> getPathComponents(File file) {
+ private static List<String> getPathComponents(File file) {
ArrayList<String> path = new ArrayList<String>();
while (file != null) {
File parentFile = file.getParentFile();
if (parentFile == null) {
- path.add(0, file.getPath());
+ path.add(file.getPath());
} else {
- path.add(0, file.getName());
+ path.add(file.getName());
}
file = parentFile;
}
- return path;
+ return Lists.reverse(path);
}
}
diff --git a/util/src/main/java/org/jf/util/SmaliHelpFormatter.java b/util/src/main/java/org/jf/util/SmaliHelpFormatter.java
deleted file mode 100644
index 3d0137e4..00000000
--- a/util/src/main/java/org/jf/util/SmaliHelpFormatter.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * [The "BSD licence"]
- * Copyright (c) 2010 Ben Gruver (JesusFreke)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.jf.util;
-
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Options;
-
-import java.io.PrintWriter;
-
-public class SmaliHelpFormatter extends HelpFormatter {
- public void printHelp(String cmdLineSyntax, String header, Options options, Options debugOptions) {
- super.printHelp(cmdLineSyntax, header, options, "");
- if (debugOptions != null) {
- System.out.println();
- System.out.println("Debug Options:");
- PrintWriter pw = new PrintWriter(System.out);
- super.printOptions(pw, getWidth(), debugOptions, getLeftPadding(), getDescPadding());
- pw.flush();
- }
- }
-}
diff --git a/util/src/main/java/org/jf/util/StringWrapper.java b/util/src/main/java/org/jf/util/StringWrapper.java
index 91808300..304c2972 100644
--- a/util/src/main/java/org/jf/util/StringWrapper.java
+++ b/util/src/main/java/org/jf/util/StringWrapper.java
@@ -33,9 +33,92 @@ package org.jf.util;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.io.PrintStream;
+import java.text.BreakIterator;
+import java.util.Iterator;
public class StringWrapper {
/**
+ * Splits the given string into lines of maximum width maxWidth. The splitting is done using the current locale's
+ * rules for splitting lines.
+ *
+ * @param string The string to split
+ * @param maxWidth The maximum length of any line
+ * @return An iterable of Strings containing the wrapped lines
+ */
+ public static Iterable<String> wrapStringOnBreaks(@Nonnull final String string, final int maxWidth) {
+ // TODO: should we strip any trailing newlines?
+ final BreakIterator breakIterator = BreakIterator.getLineInstance();
+ breakIterator.setText(string);
+
+ return new Iterable<String>() {
+ @Override
+ public Iterator<String> iterator() {
+ return new Iterator<String>() {
+ private int currentLineStart = 0;
+ private boolean nextLineSet = false;
+ private String nextLine;
+
+ @Override
+ public boolean hasNext() {
+ if (!nextLineSet) {
+ calculateNext();
+ }
+ return nextLine != null;
+ }
+
+ private void calculateNext() {
+ int lineEnd = currentLineStart;
+ while (true) {
+ lineEnd = breakIterator.following(lineEnd);
+ if (lineEnd == BreakIterator.DONE) {
+ lineEnd = breakIterator.last();
+ if (lineEnd <= currentLineStart) {
+ nextLine = null;
+ nextLineSet = true;
+ return;
+ }
+ break;
+ }
+
+ if (lineEnd - currentLineStart > maxWidth) {
+ lineEnd = breakIterator.preceding(lineEnd);
+ if (lineEnd <= currentLineStart) {
+ lineEnd = currentLineStart + maxWidth;
+ }
+ break;
+ }
+
+ if (string.charAt(lineEnd-1) == '\n') {
+ nextLine = string.substring(currentLineStart, lineEnd-1);
+ nextLineSet = true;
+ currentLineStart = lineEnd;
+ return;
+ }
+ }
+ nextLine = string.substring(currentLineStart, lineEnd);
+ nextLineSet = true;
+ currentLineStart = lineEnd;
+ }
+
+ @Override
+ public String next() {
+ String ret = nextLine;
+ nextLine = null;
+ nextLineSet = false;
+ return ret;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ /**
* Splits the given string into lines using on any embedded newlines, and wrapping the text as needed to conform to
* the given maximum line width.
*
@@ -103,4 +186,14 @@ public class StringWrapper {
System.arraycopy(arr, 0, newArr, 0, arr.length);
return newArr;
}
+
+ public static void printWrappedString(@Nonnull PrintStream stream, @Nonnull String string) {
+ printWrappedString(stream, string, ConsoleUtil.getConsoleWidth());
+ }
+
+ public static void printWrappedString(@Nonnull PrintStream stream, @Nonnull String string, int maxWidth) {
+ for (String str: wrapStringOnBreaks(string, maxWidth)) {
+ stream.println(str);
+ }
+ }
}
diff --git a/util/src/main/java/org/jf/util/WrappedIndentingWriter.java b/util/src/main/java/org/jf/util/WrappedIndentingWriter.java
index eb1acdae..df4575bd 100644
--- a/util/src/main/java/org/jf/util/WrappedIndentingWriter.java
+++ b/util/src/main/java/org/jf/util/WrappedIndentingWriter.java
@@ -1,18 +1,18 @@
/*
- * Copyright 2013, Google Inc.
+ * Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
- * * Redistributions of source code must retain the above copyright
+ * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
+ * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
- * * Neither the name of Google Inc. nor the names of its
+ * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
@@ -31,154 +31,94 @@
package org.jf.util;
+import com.google.common.collect.Lists;
+
import java.io.FilterWriter;
import java.io.IOException;
import java.io.Writer;
+import java.util.List;
-/**
- * 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 WrappedIndentingWriter extends FilterWriter {
- /** null-ok; optional prefix for every line */
- private final String prefix;
-
- /** &gt; 0; the maximum output width */
- private final int width;
+public class WrappedIndentingWriter extends FilterWriter {
- /** &gt; 0; the maximum indent */
private final int maxIndent;
+ private final int maxWidth;
- /** &gt;= 0; current output column (zero-based) */
- private int column;
-
- /** whether indent spaces are currently being collected */
- private boolean collectingIndent;
-
- /** &gt;= 0; current indent amount */
- private int indent;
+ private int currentIndent = 0;
+ private final StringBuilder line = new StringBuilder();
- /**
- * Constructs an instance.
- *
- * @param out non-null; writer to send final output to
- * @param width &gt;= 0; the maximum output width (not including
- * <code>prefix</code>), or <code>0</code> for no maximum
- * @param prefix non-null; the prefix for each line
- */
- public WrappedIndentingWriter(Writer out, int width, String prefix) {
+ public WrappedIndentingWriter(Writer out, int maxIndent, int maxWidth) {
super(out);
+ this.maxIndent = maxIndent;
+ this.maxWidth = maxWidth;
+ }
- if (out == null) {
- throw new NullPointerException("out == null");
+ private void writeIndent() throws IOException {
+ for (int i=0; i<getIndent(); i++) {
+ write(' ');
}
+ }
- if (width < 0) {
- throw new IllegalArgumentException("width < 0");
+ private int getIndent() {
+ if (currentIndent < 0) {
+ return 0;
}
-
- if (prefix == null) {
- throw new NullPointerException("prefix == null");
+ if (currentIndent > maxIndent) {
+ return maxIndent;
}
+ return currentIndent;
+ }
- this.width = (width != 0) ? width : Integer.MAX_VALUE;
- this.maxIndent = width >> 1;
- this.prefix = (prefix.length() == 0) ? null : prefix;
-
- bol();
+ public void indent(int indent) {
+ currentIndent += indent;
}
- /**
- * Constructs a no-prefix instance.
- *
- * @param out non-null; writer to send final output to
- * @param width &gt;= 0; the maximum output width (not including
- * <code>prefix</code>), or <code>0</code> for no maximum
- */
- public WrappedIndentingWriter(Writer out, int width) {
- this(out, width, "");
+ public void deindent(int indent) {
+ currentIndent -= indent;
}
- /** {@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;
- }
- }
+ private void wrapLine() throws IOException {
+ List<String> wrapped = Lists.newArrayList(StringWrapper.wrapStringOnBreaks(line.toString(), maxWidth));
+ out.write(wrapped.get(0), 0, wrapped.get(0).length());
+ out.write('\n');
- 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;
- }
+ line.replace(0, line.length(), "");
+ writeIndent();
+ for (int i=1; i<wrapped.size(); i++) {
+ if (i > 1) {
+ write('\n');
}
+ write(wrapped.get(i));
+ }
+ }
+ @Override public void write(int c) throws IOException {
+ if (c == '\n') {
+ out.write(line.toString());
out.write(c);
-
- if (c == '\n') {
- bol();
- } else {
- column++;
+ line.replace(0, line.length(), "");
+ writeIndent();
+ } else {
+ line.append((char)c);
+ if (line.length() > maxWidth) {
+ wrapLine();
}
}
}
- /** {@inheritDoc} */
- @Override
- public void write(char[] cbuf, int off, int len) throws IOException {
- synchronized (lock) {
- while (len > 0) {
- write(cbuf[off]);
- off++;
- len--;
- }
+ @Override public void write(char[] cbuf, int off, int len) throws IOException {
+ for (int i=0; i<len; i++) {
+ write(cbuf[i+off]);
}
}
- /** {@inheritDoc} */
- @Override
- public void write(String str, int off, int len) throws IOException {
- synchronized (lock) {
- while (len > 0) {
- write(str.charAt(off));
- off++;
- len--;
- }
+ @Override public void write(String str, int off, int len) throws IOException {
+ for (int i=0; i<len; i++) {
+ write(str.charAt(i+off));
}
}
- /**
- * Indicates that output is at the beginning of a line.
- */
- private void bol() {
- column = 0;
- collectingIndent = (maxIndent != 0);
- indent = 0;
+ @Override public void flush() throws IOException {
+ out.write(line.toString());
+ line.replace(0, line.length(), "");
}
}
diff --git a/util/src/main/java/org/jf/util/jcommander/ColonParameterSplitter.java b/util/src/main/java/org/jf/util/jcommander/ColonParameterSplitter.java
new file mode 100644
index 00000000..eb628aff
--- /dev/null
+++ b/util/src/main/java/org/jf/util/jcommander/ColonParameterSplitter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util.jcommander;
+
+import com.beust.jcommander.converters.IParameterSplitter;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A JCommander parameter splitter that splits a parameter value by colon
+ */
+public class ColonParameterSplitter implements IParameterSplitter {
+ @Override
+ public List<String> split(String value) {
+ return Arrays.asList(value.split(":"));
+ }
+}
diff --git a/util/src/main/java/org/jf/util/jcommander/Command.java b/util/src/main/java/org/jf/util/jcommander/Command.java
new file mode 100644
index 00000000..8fac0fab
--- /dev/null
+++ b/util/src/main/java/org/jf/util/jcommander/Command.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util.jcommander;
+
+import com.beust.jcommander.JCommander;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.jf.util.ConsoleUtil;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+public abstract class Command {
+
+ @Nonnull
+ protected final List<JCommander> commandAncestors;
+
+ public Command(@Nonnull List<JCommander> commandAncestors) {
+ this.commandAncestors = commandAncestors;
+ }
+
+ public void usage() {
+ System.out.println(new HelpFormatter()
+ .width(ConsoleUtil.getConsoleWidth())
+ .format(getCommandHierarchy()));
+ }
+
+ protected void setupCommand(JCommander jc) {
+ }
+
+ protected JCommander getJCommander() {
+ JCommander parentJc = Iterables.getLast(commandAncestors);
+ return parentJc.getCommands().get(this.getClass().getAnnotation(ExtendedParameters.class).commandName());
+ }
+
+ public List<JCommander> getCommandHierarchy() {
+ List<JCommander> commandHierarchy = Lists.newArrayList(commandAncestors);
+ commandHierarchy.add(getJCommander());
+ return commandHierarchy;
+ }
+
+ public abstract void run();
+}
diff --git a/util/src/main/java/org/jf/util/jcommander/ExtendedCommands.java b/util/src/main/java/org/jf/util/jcommander/ExtendedCommands.java
new file mode 100644
index 00000000..209d94e2
--- /dev/null
+++ b/util/src/main/java/org/jf/util/jcommander/ExtendedCommands.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util.jcommander;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterDescription;
+import com.beust.jcommander.Parameterized;
+import com.beust.jcommander.Parameters;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.lang.reflect.Field;
+
+/**
+ * Utilities related to "extended" commands - JCommander commands with additional information
+ */
+public class ExtendedCommands {
+
+ @Nonnull
+ private static ExtendedParameters getExtendedParameters(Object command) {
+ ExtendedParameters anno = command.getClass().getAnnotation(ExtendedParameters.class);
+ if (anno == null) {
+ throw new IllegalStateException("All extended commands should have an ExtendedParameters annotation: " +
+ command.getClass().getCanonicalName());
+ }
+ return anno;
+ }
+
+ @Nonnull
+ public static String commandName(JCommander jc) {
+ return getExtendedParameters(jc.getObjects().get(0)).commandName();
+ }
+
+ @Nonnull
+ public static String commandName(Object command) {
+ return getExtendedParameters(command).commandName();
+ }
+
+ @Nonnull
+ public static String[] commandAliases(JCommander jc) {
+ return commandAliases(jc.getObjects().get(0));
+ }
+
+ @Nonnull
+ public static String[] commandAliases(Object command) {
+ return getExtendedParameters(command).commandAliases();
+ }
+
+ public static boolean includeParametersInUsage(JCommander jc) {
+ return includeParametersInUsage(jc.getObjects().get(0));
+ }
+
+ public static boolean includeParametersInUsage(Object command) {
+ return getExtendedParameters(command).includeParametersInUsage();
+ }
+
+ @Nonnull
+ public static String postfixDescription(JCommander jc) {
+ return postfixDescription(jc.getObjects().get(0));
+ }
+
+ @Nonnull
+ public static String postfixDescription(Object command) {
+ return getExtendedParameters(command).postfixDescription();
+ }
+
+ public static void addExtendedCommand(JCommander jc, Command command) {
+ jc.addCommand(commandName(command), command, commandAliases(command));
+ command.setupCommand(command.getJCommander());
+ }
+
+ @Nonnull
+ public static String[] parameterArgumentNames(ParameterDescription parameterDescription) {
+ Parameterized parameterized = parameterDescription.getParameterized();
+
+ Class cls = parameterDescription.getObject().getClass();
+ Field field = null;
+ while (cls != Object.class) {
+ try {
+ field = cls.getDeclaredField(parameterized.getName());
+ } catch (NoSuchFieldException ex) {
+ cls = cls.getSuperclass();
+ continue;
+ }
+ break;
+ }
+
+ assert field != null;
+ ExtendedParameter extendedParameter = field.getAnnotation(ExtendedParameter.class);
+ if (extendedParameter != null) {
+ return extendedParameter.argumentNames();
+ }
+
+ return new String[0];
+ }
+
+ @Nullable
+ public static JCommander getSubcommand(JCommander jc, String commandName) {
+ if (jc.getCommands().containsKey(commandName)) {
+ return jc.getCommands().get(commandName);
+ } else {
+ for (JCommander command : jc.getCommands().values()) {
+ for (String alias: commandAliases(command)) {
+ if (commandName.equals(alias)) {
+ return command;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public static String getCommandDescription(@Nonnull JCommander jc) {
+ Parameters parameters = jc.getObjects().get(0).getClass().getAnnotation(Parameters.class);
+ if (parameters == null) {
+ return null;
+ }
+ return parameters.commandDescription();
+ }
+}
diff --git a/util/src/main/java/org/jf/util/jcommander/ExtendedParameter.java b/util/src/main/java/org/jf/util/jcommander/ExtendedParameter.java
new file mode 100644
index 00000000..81f78c22
--- /dev/null
+++ b/util/src/main/java/org/jf/util/jcommander/ExtendedParameter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util.jcommander;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExtendedParameter {
+ String[] argumentNames();
+}
diff --git a/util/src/main/java/org/jf/util/jcommander/ExtendedParameters.java b/util/src/main/java/org/jf/util/jcommander/ExtendedParameters.java
new file mode 100644
index 00000000..965d2b2a
--- /dev/null
+++ b/util/src/main/java/org/jf/util/jcommander/ExtendedParameters.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util.jcommander;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExtendedParameters {
+ boolean includeParametersInUsage() default false;
+ String commandName();
+ String[] commandAliases() default { };
+ String postfixDescription() default "";
+}
diff --git a/util/src/main/java/org/jf/util/jcommander/HelpFormatter.java b/util/src/main/java/org/jf/util/jcommander/HelpFormatter.java
new file mode 100644
index 00000000..e807d5fe
--- /dev/null
+++ b/util/src/main/java/org/jf/util/jcommander/HelpFormatter.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2016, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.jf.util.jcommander;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterDescription;
+import com.beust.jcommander.Parameters;
+import com.beust.jcommander.internal.Lists;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import org.jf.util.WrappedIndentingWriter;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class HelpFormatter {
+
+ private int width = 80;
+
+ @Nonnull
+ public HelpFormatter width(int width) {
+ this.width = width;
+ return this;
+ }
+
+ @Nonnull
+ private static ExtendedParameters getExtendedParameters(JCommander jc) {
+ ExtendedParameters anno = jc.getObjects().get(0).getClass().getAnnotation(ExtendedParameters.class);
+ if (anno == null) {
+ throw new IllegalStateException("All commands should have an ExtendedParameters annotation");
+ }
+ return anno;
+ }
+
+ @Nonnull
+ private static List<String> getCommandAliases(JCommander jc) {
+ return Lists.newArrayList(getExtendedParameters(jc).commandAliases());
+ }
+
+ private static boolean includeParametersInUsage(@Nonnull JCommander jc) {
+ return getExtendedParameters(jc).includeParametersInUsage();
+ }
+
+ @Nonnull
+ private static String getPostfixDescription(@Nonnull JCommander jc) {
+ return getExtendedParameters(jc).postfixDescription();
+ }
+
+ private int getParameterArity(ParameterDescription param) {
+ if (param.getParameter().arity() > 0) {
+ return param.getParameter().arity();
+ }
+ Class<?> type = param.getParameterized().getType();
+ if ((type == boolean.class || type == Boolean.class)) {
+ return 0;
+ }
+ return 1;
+ }
+
+ private List<ParameterDescription> getSortedParameters(JCommander jc) {
+ List<ParameterDescription> parameters = Lists.newArrayList(jc.getParameters());
+
+ final Pattern pattern = Pattern.compile("^-*(.*)$");
+
+ Collections.sort(parameters, new Comparator<ParameterDescription>() {
+ @Override public int compare(ParameterDescription o1, ParameterDescription o2) {
+ String s1;
+ Matcher matcher = pattern.matcher(o1.getParameter().names()[0]);
+ if (matcher.matches()) {
+ s1 = matcher.group(1);
+ } else {
+ throw new IllegalStateException();
+ }
+
+ String s2;
+ matcher = pattern.matcher(o2.getParameter().names()[0]);
+ if (matcher.matches()) {
+ s2 = matcher.group(1);
+ } else {
+ throw new IllegalStateException();
+ }
+
+ return s1.compareTo(s2);
+ }
+ });
+ return parameters;
+ }
+
+ @Nonnull
+ public String format(@Nonnull JCommander... jc) {
+ return format(Arrays.asList(jc));
+ }
+
+ @Nonnull
+ public String format(@Nonnull List<JCommander> commandHierarchy) {
+ try {
+ StringWriter stringWriter = new StringWriter();
+ WrappedIndentingWriter writer = new WrappedIndentingWriter(stringWriter, width - 5, width);
+
+ JCommander leafJc = Iterables.getLast(commandHierarchy);
+
+ writer.write("usage:");
+ writer.indent(2);
+
+ for (JCommander jc: commandHierarchy) {
+ writer.write(" ");
+ writer.write(ExtendedCommands.commandName(jc));
+ }
+
+ if (includeParametersInUsage(leafJc)) {
+ for (ParameterDescription param : leafJc.getParameters()) {
+ if (!param.getParameter().hidden()) {
+ writer.write(" [");
+ writer.write(param.getParameter().getParameter().names()[0]);
+ writer.write("]");
+ }
+ }
+ } else {
+ if (!leafJc.getParameters().isEmpty()) {
+ writer.write(" [<options>]");
+ }
+ }
+
+ if (!leafJc.getCommands().isEmpty()) {
+ writer.write(" [<command [<args>]]");
+ }
+
+ if (leafJc.getMainParameter() != null) {
+ String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameter());
+ if (argumentNames.length == 0) {
+ writer.write(" <args>");
+ } else {
+ String argumentName = argumentNames[0];
+ boolean writeAngleBrackets = !argumentName.startsWith("<") && !argumentName.startsWith("[");
+ writer.write(" ");
+ if (writeAngleBrackets) {
+ writer.write("<");
+ }
+ writer.write(argumentNames[0]);
+ if (writeAngleBrackets) {
+ writer.write(">");
+ }
+ }
+ }
+
+ writer.deindent(2);
+
+ String commandDescription = ExtendedCommands.getCommandDescription(leafJc);
+ if (commandDescription != null) {
+ writer.write("\n");
+ writer.write(commandDescription);
+ }
+
+ if (!leafJc.getParameters().isEmpty() || leafJc.getMainParameter() != null) {
+ writer.write("\n\nOptions:");
+ writer.indent(2);
+ for (ParameterDescription param : getSortedParameters(leafJc)) {
+ if (!param.getParameter().hidden()) {
+ writer.write("\n");
+ writer.indent(4);
+ if (!param.getNames().isEmpty()) {
+ writer.write(Joiner.on(',').join(param.getParameter().names()));
+ }
+ if (getParameterArity(param) > 0) {
+ String[] argumentNames = ExtendedCommands.parameterArgumentNames(param);
+ for (int i = 0; i < getParameterArity(param); i++) {
+ writer.write(" ");
+ if (i < argumentNames.length) {
+ writer.write("<");
+ writer.write(argumentNames[i]);
+ writer.write(">");
+ } else {
+ writer.write("<arg>");
+ }
+ }
+ }
+ if (param.getDescription() != null && !param.getDescription().isEmpty()) {
+ writer.write(" - ");
+ writer.write(param.getDescription());
+ }
+ if (param.getDefault() != null) {
+ String defaultValue = null;
+ if (param.getParameterized().getType() == Boolean.class ||
+ param.getParameterized().getType() == Boolean.TYPE) {
+ if ((Boolean)param.getDefault()) {
+ defaultValue = "True";
+ }
+ } else if (List.class.isAssignableFrom(param.getParameterized().getType())) {
+ if (!((List)param.getDefault()).isEmpty()) {
+ defaultValue = param.getDefault().toString();
+ }
+ } else {
+ defaultValue = param.getDefault().toString();
+ }
+ if (defaultValue != null) {
+ writer.write(" (default: ");
+ writer.write(defaultValue);
+ writer.write(")");
+ }
+ }
+ writer.deindent(4);
+ }
+ }
+
+ if (leafJc.getMainParameter() != null) {
+ String[] argumentNames = ExtendedCommands.parameterArgumentNames(leafJc.getMainParameter());
+ writer.write("\n");
+ writer.indent(4);
+ if (argumentNames.length > 0) {
+ writer.write("<");
+ writer.write(argumentNames[0]);
+ writer.write(">");
+ } else {
+ writer.write("<args>");
+ }
+
+ if (leafJc.getMainParameterDescription() != null) {
+ writer.write(" - ");
+ writer.write(leafJc.getMainParameterDescription());
+ }
+ writer.deindent(4);
+ }
+ writer.deindent(2);
+ }
+
+ if (!leafJc.getCommands().isEmpty()) {
+ writer.write("\n\nCommands:");
+ writer.indent(2);
+
+
+ List<Entry<String, JCommander>> entryList = Lists.newArrayList(leafJc.getCommands().entrySet());
+ Collections.sort(entryList, new Comparator<Entry<String, JCommander>>() {
+ @Override public int compare(Entry<String, JCommander> o1, Entry<String, JCommander> o2) {
+ return o1.getKey().compareTo(o2.getKey());
+ }
+ });
+
+ for (Entry<String, JCommander> entry : entryList) {
+ String commandName = entry.getKey();
+ JCommander command = entry.getValue();
+
+ Object arg = command.getObjects().get(0);
+ Parameters parametersAnno = arg.getClass().getAnnotation(Parameters.class);
+ if (!parametersAnno.hidden()) {
+ writer.write("\n");
+ writer.indent(4);
+ writer.write(commandName);
+ List<String> aliases = getCommandAliases(command);
+ if (!aliases.isEmpty()) {
+ writer.write("(");
+ writer.write(Joiner.on(',').join(aliases));
+ writer.write(")");
+ }
+
+ String commandDesc = leafJc.getCommandDescription(commandName);
+ if (commandDesc != null) {
+ writer.write(" - ");
+ writer.write(commandDesc);
+ }
+ writer.deindent(4);
+ }
+ }
+ writer.deindent(2);
+ }
+
+ String postfixDescription = getPostfixDescription(leafJc);
+ if (!postfixDescription.isEmpty()) {
+ writer.write("\n\n");
+ writer.write(postfixDescription);
+ }
+
+ writer.flush();
+
+ return stringWriter.getBuffer().toString();
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+}
diff --git a/util/src/test/java/org/jf/util/StringWrapperTest.java b/util/src/test/java/org/jf/util/StringWrapperTest.java
index 64dca33e..94c79142 100644
--- a/util/src/test/java/org/jf/util/StringWrapperTest.java
+++ b/util/src/test/java/org/jf/util/StringWrapperTest.java
@@ -31,11 +31,35 @@
package org.jf.util;
+import com.google.common.collect.Lists;
import org.junit.Assert;
import org.junit.Test;
+import java.util.List;
+
public class StringWrapperTest {
@Test
+ public void testWrapStringByWords() {
+ validateResult2(new String[]{"abc", "abcdef", "abcdef"},
+ "abc\nabcdefabcdef", 6);
+
+ validateResult2(new String[]{"abc", "abcdef", " ", "abcdef"},
+ "abc\nabcdef abcdef", 6);
+
+ validateResult2(new String[]{"abc", "abcde ", "fabcde", "f"},
+ "abc\nabcde fabcdef", 6);
+
+ validateResult2(new String[]{"abc def ghi ", "kjl mon pqr ", "stu vwx yz"},
+ "abc def ghi kjl mon pqr stu vwx yz", 14);
+
+ validateResult2(new String[]{"abcdefg", "hikjlmo", "npqrstu", "vwxyz"},
+ "abcdefghikjlmonpqrstuvwxyz", 7);
+
+ validateResult2(new String[]{"abc", "defhig"},
+ "abc\ndefhig", 20);
+ }
+
+ @Test
public void testWrapString() {
validateResult(
new String[]{"abc", "abcdef", "abcdef"},
@@ -115,4 +139,15 @@ public class StringWrapperTest {
Assert.assertEquals(expected[i], actual[i]);
}
}
+
+ public static void validateResult2(String[] expected, String textToWrap, int maxWidth) {
+ List<String> result = Lists.newArrayList(StringWrapper.wrapStringOnBreaks(textToWrap, maxWidth));
+
+ Assert.assertEquals(expected.length, result.size());
+ int i;
+ for (i=0; i<result.size(); i++) {
+ Assert.assertTrue(i < expected.length);
+ Assert.assertEquals(expected[i], result.get(i));
+ }
+ }
}