Merge branch 'v2.2_WIP'
diff --git a/baksmali/build.gradle b/baksmali/build.gradle
index f3a14b1..3c8c476 100644
--- a/baksmali/build.gradle
+++ b/baksmali/build.gradle
@@ -41,8 +41,8 @@
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 @@
classifier = 'fat'
manifest {
- attributes('Main-Class': 'org.jf.baksmali.main')
+ attributes('Main-Class': 'org.jf.baksmali.Main')
}
doLast {
@@ -92,7 +92,7 @@
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[]); }'
keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
dontwarn 'com.google.common.**'
@@ -100,3 +100,17 @@
}
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 6c67d4a..4b545ee 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 @@
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 2529af8..361826d 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.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 @@
}
@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 @@
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 aed315d..2680704 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 ae01791..90291b7 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.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 @@
writer.write(" = ");
String containingClass = null;
- if (options.useImplicitReferences) {
+ if (options.implicitReferences) {
containingClass = field.getDefiningClass();
}
@@ -82,7 +82,7 @@
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 fe85fe0..d58b2b6 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.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 @@
}
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 @@
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 3ffb4bd..be76edf 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 @@
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 @@
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 b152bb6..268d643 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 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 ef2110a..9332111 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.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 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 @@
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 @@
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 @@
}
String containingClass = null;
- if (classDef.options.useImplicitReferences) {
+ if (classDef.options.implicitReferences) {
containingClass = method.getDefiningClass();
}
AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
@@ -313,18 +313,18 @@
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 @@
writer.indent(4);
String containingClass = null;
- if (options.useImplicitReferences) {
+ if (options.implicitReferences) {
containingClass = method.getDefiningClass();
}
AnnotationFormatter.writeTo(writer, annotations, containingClass);
@@ -374,11 +374,11 @@
}
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 @@
methodItems.add(new BlankMethodItem(currentCodeAddress));
}
- if (classDef.options.addCodeOffsets) {
+ if (classDef.options.codeOffsets) {
methodItems.add(new MethodItem(currentCodeAddress) {
@Override
@@ -432,7 +432,7 @@
});
}
- if (!classDef.options.noAccessorComments && (instruction instanceof ReferenceInstruction)) {
+ if (classDef.options.accessorComments && (instruction instanceof ReferenceInstruction)) {
Opcode opcode = instruction.getOpcode();
if (opcode.referenceType == ReferenceType.METHOD) {
@@ -493,7 +493,7 @@
methodItems.add(new BlankMethodItem(currentCodeAddress));
}
- if (classDef.options.addCodeOffsets) {
+ if (classDef.options.codeOffsets) {
methodItems.add(new MethodItem(currentCodeAddress) {
@Override
@@ -597,7 +597,7 @@
@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 812a282..62826b1 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 @@
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 f532938..f934edd 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 @@
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 bffcb38..3d72f46 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 @@
* 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 @@
* @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 @@
* @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 0000000..83c4b13
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/AnalysisArguments.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+public class AnalysisArguments {
+ @Parameter(names = {"-a", "--api"},
+ description = "The numeric api level of the file being disassembled.")
+ @ExtendedParameter(argumentNames = "api")
+ public int apiLevel = 15;
+
+ @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, 0);
+ }
+
+ @Nonnull
+ public ClassPath loadClassPathForDexFile(@Nonnull File dexFileDir, @Nonnull DexFile dexFile,
+ boolean checkPackagePrivateAccess, int oatVersion)
+ throws IOException {
+ ClassPathResolver resolver;
+
+ if (dexFile instanceof OatDexFile) {
+ 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, apiLevel);
+ } 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
similarity index 60%
rename from baksmali/src/main/java/org/jf/baksmali/baksmali.java
rename to baksmali/src/main/java/org/jf/baksmali/Baksmali.java
index 5060734..a2fc411 100644
--- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Baksmali.java
@@ -28,105 +28,20 @@
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 java.io.*;
import java.util.List;
-import java.util.Map.Entry;
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;
- }
- }
- }
-
- 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 class Baksmali {
+ public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options) {
//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,13 +49,9 @@
//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(outputDir, ".smali");
- final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");
-
- ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
+ ExecutorService executor = Executors.newFixedThreadPool(jobs);
List<Future<Boolean>> tasks = Lists.newArrayList();
for (final ClassDef classDef: classDefs) {
@@ -174,7 +85,7 @@
}
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
new file mode 100644
index 0000000..7ad5124
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/BaksmaliOptions.java
@@ -0,0 +1,108 @@
+/*
+ * 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.baksmali;
+
+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.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+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;
+ public static final int ALLPOST = 4;
+ public static final int ARGS = 8;
+ public static final int DEST = 16;
+ public static final int MERGE = 32;
+ public static final int FULLMERGE = 64;
+
+ public int registerInfo = 0;
+
+ public Map<Integer,String> resourceIds = new HashMap<Integer,String>();
+ public InlineMethodResolver inlineResolver = null;
+ public ClassPath classPath = null;
+ public SyntheticAccessorResolver syntheticAccessorResolver = null;
+
+ /**
+ * 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 0000000..3ded479
--- /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 0000000..7117bb2
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DexInputCommand.java
@@ -0,0 +1,146 @@
+/*
+ * 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(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.
+ * @param opcodes The set of opcodes to load the dex file with.
+ */
+ protected void loadDexFile(@Nonnull String input, Opcodes opcodes) {
+ 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);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ } else {
+ try {
+ dexFile = DexFileFactory.loadDexFile(file, opcodes);
+ } 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 0000000..1ce4952
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DisassembleCommand.java
@@ -0,0 +1,283 @@
+/*
+ * 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.Opcodes;
+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;
+
+ 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, Opcodes.getDefault());
+
+ 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())) {
+ 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 0000000..94c7bb3
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/DumpCommand.java
@@ -0,0 +1,114 @@
+/*
+ * 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.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 org.jf.util.jcommander.ExtendedParameter;
+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;
+
+ @Parameter(names = {"-a", "--api"},
+ description = "The numeric api level of the file being disassembled.")
+ @ExtendedParameter(argumentNames = "api")
+ private int apiLevel = 15;
+
+ 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, Opcodes.getDefault());
+
+ try {
+ dump(dexFile, System.out, apiLevel);
+ } 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.
+ * @param apiLevel The api level to use when dumping the dex file
+ *
+ * @throws IOException
+ */
+ public static void dump(@Nonnull DexBackedDexFile dexFile, @Nonnull OutputStream output, int apiLevel)
+ 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 0000000..149ac63
--- /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 0000000..6c6ca64
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListClassesCommand.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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import org.jf.dexlib2.Opcodes;
+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, Opcodes.getDefault());
+
+ 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 0000000..9547620
--- /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 0000000..636a87c
--- /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 0000000..d5862eb
--- /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 0000000..68e2050
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListFieldOffsetsCommand.java
@@ -0,0 +1,121 @@
+/*
+ * 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.Opcodes;
+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, Opcodes.getDefault());
+ 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 = analysisArguments.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 0000000..c4d090d
--- /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 0000000..2e64286
--- /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 0000000..603e764
--- /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 0000000..3403bbf
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListReferencesCommand.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.baksmali;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import org.jf.dexlib2.Opcodes;
+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, Opcodes.getDefault());
+
+ 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 0000000..8694f91
--- /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 0000000..fbff2f2
--- /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 0000000..ed88c12
--- /dev/null
+++ b/baksmali/src/main/java/org/jf/baksmali/ListVtablesCommand.java
@@ -0,0 +1,158 @@
+/*
+ * 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.Opcodes;
+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, Opcodes.getDefault());
+
+ 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 = analysisArguments.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 0000000..66d9b4f
--- /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/baksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java
deleted file mode 100644
index 32685dd..0000000
--- a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java
+++ /dev/null
@@ -1,108 +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.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 javax.annotation.Nullable;
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class baksmaliOptions {
- // register info values
- public static final int ALL = 1;
- public static final int ALLPRE = 2;
- public static final int ALLPOST = 4;
- public static final int ARGS = 8;
- public static final int DEST = 16;
- 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 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]);
- }
- }
-}
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 79405e5..0000000
--- 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 2d6ed8c..0000000
--- a/baksmali/src/main/java/org/jf/baksmali/main.java
+++ /dev/null
@@ -1,612 +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 { // api >= 21
- // TODO: verify, add new ones?
- 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");
- }
- }
-}
diff --git a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
index 2bb04dd..512e160 100644
--- a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
@@ -36,6 +36,7 @@
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.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
@@ -85,14 +86,14 @@
public void runTest(String test, boolean registerInfo) 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.registerInfo = BaksmaliOptions.ALL | BaksmaliOptions.FULLMERGE;
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 1c570b6..4dd2ad9 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 @@
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 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 @@
}
@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 5a4db65..f9f5562 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 @@
}
@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 1a34e8c..769372e 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 @@
}
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 78fabc0..ad2aad5 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 @@
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 @@
@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 1f2ae5b..962a6be 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 @@
"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 @@
" 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 @@
".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 @@
".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 @@
" 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 @@
" 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 @@
".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 @@
".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 d85d791..f1ade1e 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 @@
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 c9ff2d4..81e98a3 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 @@
}
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/build.gradle b/build.gradle
index 56aaa3f..c9c8beb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -101,14 +101,15 @@
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.+',
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: 'com.beust:jcommander:1.48'
]
}
diff --git a/dexlib2/OatVersions.txt b/dexlib2/OatVersions.txt
index 8aa9ea9..abb4a83 100644
--- a/dexlib2/OatVersions.txt
+++ b/dexlib2/OatVersions.txt
@@ -8,6 +8,7 @@
- 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,27 @@
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
diff --git a/dexlib2/build.gradle b/dexlib2/build.gradle
index 8fbe5ff..422d2c3 100644
--- a/dexlib2/build.gradle
+++ b/dexlib2/build.gradle
@@ -51,6 +51,7 @@
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 60488ba..f833ac8 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
@@ -31,14 +31,19 @@
package org.jf.dexlib2;
-import com.google.common.base.MoreObjects;
-import com.google.common.io.ByteStreams;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
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.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 +51,45 @@
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);
+ 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 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);
- }
-
- @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));
- }
-
- @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
- }
- }
+ public static DexBackedDexFile loadDexFile(@Nonnull File file, @Nonnull Opcodes opcodes) throws IOException {
+ if (!file.exists()) {
+ throw new DexFileNotFoundException("%s does not exist", file.getName());
}
- InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));
+
+ try {
+ 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(file));
try {
try {
return DexBackedDexFile.fromInputStream(opcodes, inputStream);
@@ -127,14 +97,15 @@
// 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);
@@ -150,71 +121,181 @@
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;
- }
- }
-
- if (!dexEntry.contains("/")) {
- for (OatDexFile oatDexFile : oatFile.getDexFiles()) {
- File oatEntryFile = new File(oatDexFile.filename);
- if (oatEntryFile.getName().equals(dexEntry)) {
- return oatDexFile;
- }
- }
- }
-
- throw new DexFileNotFound("oat file %s does not contain a dex file named %s",
- dexFile.getName(), dexEntry);
- }
+ return oatDexFiles.get(0);
}
} 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, 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);
+ } catch (NotAnOatFileException ex) {
+ // just eat it
+ }
+
+ 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 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);
+ } 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 DexFileNotFound(@Nullable Throwable cause, @Nullable String message, Object... formatArgs) {
- super(cause, message, formatArgs);
- }
-
- public DexFileNotFound(@Nullable String message, Object... formatArgs) {
+ public static class DexFileNotFoundException extends ExceptionWithContext {
+ public DexFileNotFoundException(@Nullable String message, Object... formatArgs) {
super(message, formatArgs);
}
}
- public static class MultipleDexFilesException extends ExceptionWithContext {
- @Nonnull public final OatFile oatFile;
-
- public MultipleDexFilesException(@Nonnull OatFile oatFile) {
- super("Oat file has multiple dex files.");
- this.oatFile = oatFile;
- }
- }
-
public static class UnsupportedOatVersionException extends ExceptionWithContext {
@Nonnull public final OatFile oatFile;
@@ -223,4 +304,155 @@
this.oatFile = oatFile;
}
}
+
+ public static class MultipleMatchingDexEntriesException extends ExceptionWithContext {
+ public MultipleMatchingDexEntriesException(@Nonnull String message, Object... formatArgs) {
+ super(String.format(message, formatArgs));
+ }
+ }
+
+ public static class UnsupportedFileTypeException extends ExceptionWithContext {
+ public UnsupportedFileTypeException(@Nonnull String message, Object... formatArgs) {
+ super(String.format(message, formatArgs));
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
+
+ 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;
+ }
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java b/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java
index 138c6c6..1d7a12d 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 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 @@
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 17f8013..c1e40ad 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
@@ -39,6 +39,10 @@
import java.util.EnumMap;
import java.util.HashMap;
+import static org.jf.dexlib2.VersionMap.NO_VERSION;
+import static org.jf.dexlib2.VersionMap.mapApiToArtVersion;
+import static org.jf.dexlib2.VersionMap.mapArtVersionToApi;
+
public class Opcodes {
/**
@@ -52,37 +56,36 @@
@Nonnull
public static Opcodes forApi(int api) {
- return new Opcodes(api, VersionMap.mapApiToArtVersion(api), false);
- }
-
- @Nonnull
- public static Opcodes forApi(int api, boolean experimental) {
- return new Opcodes(api, VersionMap.mapApiToArtVersion(api), 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(VersionMap.mapArtVersionToApi(artVersion), 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) {
- this.api = api;
- this.artVersion = artVersion;
+ if (api >= 21) {
+ this.api = api;
+ this.artVersion = mapApiToArtVersion(api);
+ } else if (artVersion >= 0 && artVersion < 39) {
+ this.api = mapArtVersionToApi(artVersion);
+ this.artVersion = artVersion;
+ } else {
+ this.api = api;
+ this.artVersion = artVersion;
+ }
opcodeValues = new EnumMap<Opcode, Short>(Opcode.class);
opcodesByName = Maps.newHashMap();
@@ -104,7 +107,7 @@
}
Short opcodeValue = versionToValueMap.get(version);
- if (opcodeValue != null && (!opcode.isExperimental() || experimental)) {
+ if (opcodeValue != null) {
if (!opcode.format.isPayloadFormat) {
opcodesByValue[opcodeValue] = opcode;
}
@@ -142,6 +145,6 @@
}
public boolean isArt() {
- return artVersion != VersionMap.NO_VERSION;
+ return artVersion != NO_VERSION;
}
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/VersionMap.java b/dexlib2/src/main/java/org/jf/dexlib2/VersionMap.java
index 42802bc..e0e1a6b 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/VersionMap.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/VersionMap.java
@@ -35,16 +35,38 @@
public static final int NO_VERSION = -1;
public static int mapArtVersionToApi(int artVersion) {
- // TODO: implement this
- return 20;
+ if (artVersion >= 79) {
+ return 24;
+ }
+ if (artVersion >= 64) {
+ return 23;
+ }
+ if (artVersion >= 45) {
+ return 22;
+ }
+ if (artVersion >= 39) {
+ return 21;
+ }
+ return 19;
}
public static int mapApiToArtVersion(int api) {
- // TODO: implement this
- if (api < 20) {
- return NO_VERSION;
- } else {
- return 56;
+ switch (api) {
+ case 19:
+ case 20:
+ return 7;
+ case 21:
+ return 39;
+ case 22:
+ return 45;
+ case 23:
+ return 64;
+ case 24:
+ return 79;
}
+ if (api > 24) {
+ return 79;
+ }
+ return NO_VERSION;
}
}
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 55f1ddc..111913c 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/AnalyzedInstruction.java
@@ -54,7 +54,7 @@
/**
* The actual instruction
*/
- @Nullable
+ @Nonnull
protected Instruction instruction;
/**
@@ -65,21 +65,25 @@
/**
* 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 +98,8 @@
*/
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 +154,17 @@
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 +187,7 @@
if (predecessors.size() == 0) {
return false;
}
-
- if (predecessors.first().instructionIndex == -1) {
- return true;
- }
- return false;
+ return predecessors.first().instructionIndex == -1;
}
/*
@@ -237,6 +236,7 @@
* @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 +249,10 @@
}
}
}
+ if (mergedRegisterType == null) {
+ // This is a start-of-method or unreachable instruction.
+ throw new IllegalStateException();
+ }
return mergedRegisterType;
}
/**
@@ -275,10 +279,10 @@
*
* 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
*/
@@ -309,7 +313,7 @@
}
protected boolean isInvokeInit() {
- if (instruction == null || !instruction.getOpcode().canInitializeReference()) {
+ if (!instruction.getOpcode().canInitializeReference()) {
return false;
}
@@ -364,10 +368,10 @@
return false;
}
- if (instruction.getOpcode() == Opcode.IF_EQZ || instruction.getOpcode() == Opcode.IF_NEZ) {
+ if (getPredecessorCount() == 1 && (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())) {
@@ -421,7 +425,7 @@
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 9f9e396..0136374 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.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;
@@ -114,7 +104,7 @@
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 +154,6 @@
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 0000000..82525bb
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPathResolver.java
@@ -0,0 +1,442 @@
+/*
+ * 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, dexFile.getOpcodes().api);
+ }
+
+ /**
+ * 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
+ * @param apiLevel The api level of the device. This is used to select an appropriate set of boot classpath entries.
+ * @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, int apiLevel)
+ throws IOException {
+ this(bootClassPathDirs, null, extraClassPathEntries, dexFile, apiLevel);
+ }
+
+ private ClassPathResolver(@Nonnull List<String> bootClassPathDirs, @Nullable List<String> bootClassPathEntries,
+ @Nonnull List<String> extraClassPathEntries, @Nonnull DexFile dexFile, int apiLevel)
+ throws IOException {
+ this.classPathDirs = bootClassPathDirs;
+ opcodes = dexFile.getOpcodes();
+
+ if (bootClassPathEntries == null) {
+ bootClassPathEntries = getDefaultBootClassPath(dexFile, apiLevel);
+ }
+
+ 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) {
+ 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()) {
+ // TODO: print a warning in the baksmali frontend before we get here
+ 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");
+ }
+
+ 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");
+ }
+ // TODO: update for N
+ }
+}
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 70e3793..cc8a53c 100755
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java
@@ -39,12 +39,10 @@
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;
@@ -53,6 +51,7 @@
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
@@ -123,11 +122,18 @@
*/
@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);
@@ -149,7 +155,8 @@
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) {
@@ -159,6 +166,7 @@
}
}
} catch (UnresolvedClassException ex) {
+ interfaces.put(type, null);
unresolvedInterfaces.add(type);
interfacesFullyResolved = false;
}
@@ -197,6 +205,71 @@
}
});
+ /**
+ * 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) {
@@ -379,7 +452,10 @@
}
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)) {
@@ -392,7 +468,20 @@
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 {
@@ -440,9 +529,7 @@
ClassProto superclass = null;
if (superclassType != null) {
superclass = (ClassProto) classPath.getClass(superclassType);
- if (superclass != null) {
- startFieldOffset = superclass.getNextFieldOffset();
- }
+ startFieldOffset = superclass.getNextFieldOffset();
}
int fieldIndexMod;
@@ -530,13 +617,11 @@
//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;
+ if (fieldOffset % 8 != 0) {
+ assert fieldOffset % 8 == 4;
+ fieldOffset += 4;
}
+ gotDouble = true;
}
instanceFields.append(fieldOffset, field);
@@ -574,7 +659,7 @@
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;
@@ -584,7 +669,7 @@
};
} 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;
@@ -778,12 +863,18 @@
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();
@@ -812,52 +903,315 @@
//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();
- outer: for (Method virtualMethod: methods) {
- for (int i=0; i<vtable.size(); i++) {
- Method superMethod = vtable.get(i);
- if (MethodUtil.methodSignaturesMatch(superMethod, virtualMethod)) {
- if (!classPath.shouldCheckPackagePrivateAccess() ||
- AnalyzedMethodUtil.canAccess(ClassProto.this, superMethod, true, false, false)) {
- if (replaceExisting) {
- vtable.set(i, virtualMethod);
+ //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();
+
+ 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, candidate, true, false, false)) {
+ if (interfaceMethodOverrides(interfaceMethod, candidate)) {
+ vtable.set(j, interfaceMethod);
+ }
+ }
}
- continue outer;
+ }
+
+ 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;
+ }
+
+ 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)) {
@@ -871,4 +1225,68 @@
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 2bb3e49..0000000
--- 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 193c0d3..0000000
--- 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 b7a15a0..0da57f1 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.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 @@
@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 @@
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 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 boolean setsWideRegister() {
- return false;
- }
-
- @Override
- public boolean setsRegister(int registerNumber) {
- return false;
- }
-
- @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 @@
analyze();
}
+ @Nonnull
public ClassPath getClassPath() {
return classPath;
}
@@ -1176,32 +1167,36 @@
setDestinationRegisterTypeAndPropagateChanges(analyzedInstruction, castRegisterType);
}
+ private static boolean isNarrowingConversion(RegisterType originalType, RegisterType newType) {
+ if (originalType.type == null || newType.type == null) {
+ return false;
+ }
+ if (originalType.type.isInterface()) {
+ return newType.type.implementsInterface(originalType.type.getType());
+ } else {
+ TypeProto commonSuperclass = newType.type.getCommonSuperclass(originalType.type);
+ return commonSuperclass.getType().equals(originalType.type.getType());
+ }
+ }
+
static boolean canNarrowAfterInstanceOf(AnalyzedInstruction analyzedInstanceOfInstruction,
AnalyzedInstruction analyzedIfInstruction, ClassPath classPath) {
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 isNarrowingConversion(originalType, registerType);
}
+ } catch (UnresolvedClassException ex) {
+ return false;
}
}
return false;
@@ -1216,16 +1211,47 @@
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 (analyzedInstruction.getPredecessorCount() != 1) {
+ return;
+ }
+ AnalyzedInstruction prevAnalyzedInstruction = analyzedInstruction.getPredecessors().first();
+ if (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);
+ List<Integer> narrowingRegisters = Lists.newArrayList();
+
RegisterType newType = RegisterType.getRegisterType(classPath,
(TypeReference)((Instruction22c)prevAnalyzedInstruction.instruction).getReference());
+ if (instructionIndex > 1) {
+ // If we have something like:
+ // move-object/from16 v0, p1
+ // instance-of v2, v0, Lblah;
+ // if-eqz v2, :target
+ // Then we need to narrow both v0 AND p1
+ AnalyzedInstruction prevPrevAnalyzedInstruction =
+ analyzedInstructions.valueAt(instructionIndex - 2);
+ 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 (originalType.type != null) {
+ if (isNarrowingConversion(originalType, newType)) {
+ narrowingRegisters.add(
+ ((TwoRegisterInstruction)prevPrevAnalyzedInstruction.instruction).getRegisterB());
+ }
+ }
+ }
+ }
+
+ // Propagate the original type to the failing branch, and the new type to the successful branch
+ int narrowingRegister = ((Instruction22c)prevAnalyzedInstruction.instruction).getRegisterB();
+ narrowingRegisters.add(narrowingRegister);
+ RegisterType originalType = analyzedInstruction.getPreInstructionRegisterType(narrowingRegister);
+
AnalyzedInstruction fallthroughInstruction = analyzedInstructions.valueAt(
analyzedInstruction.getInstructionIndex() + 1);
@@ -1233,16 +1259,18 @@
((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);
+ for (int register: narrowingRegisters) {
+ 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);
+ }
}
}
}
@@ -1695,13 +1723,13 @@
// 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 +1761,9 @@
targetMethod = (MethodReference)instruction.getReference();
}
- TypeProto typeProto = classPath.getClass(targetMethod.getDefiningClass());
- int methodIndex;
- try {
- methodIndex = typeProto.findMethodIndexInVtable(targetMethod);
- } catch (UnresolvedClassException ex) {
- return true;
- }
+ MethodReference replacementMethod = normalizeMethodReference(targetMethod);
- 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;
- }
- }
-
- if (replacementMethod.equals(method)) {
+ if (replacementMethod == null || replacementMethod.equals(targetMethod)) {
return true;
}
@@ -1839,7 +1835,9 @@
// 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 +1858,20 @@
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 +1972,70 @@
"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 ba782fe..75478ca 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 @@
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 4a4615a..029ddb9 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('.', '/');
}
- return javaName;
+
+ 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('/', '.');
+ }
+
+ 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 f056f24..862e342 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 @@
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 c0d38b0..2fc5ed1 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 @@
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 @@
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 3ff6f7d..f297760 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 @@
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 @@
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 c6daa91..2f13c1a 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 @@
@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/dexbacked/DexBackedDexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
index 32505ee..8e7127a 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java
@@ -33,9 +33,15 @@
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.util.ExceptionWithContext;
import javax.annotation.Nonnull;
@@ -43,6 +49,8 @@
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,7 +69,7 @@
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;
@@ -85,7 +93,7 @@
}
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,6 +104,7 @@
this(opcodes, buf, 0, true);
}
+ @Nonnull
public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is)
throws IOException {
if (!is.markSupported()) {
@@ -148,7 +157,7 @@
};
}
- private static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) {
+ protected 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++) {
@@ -265,6 +274,81 @@
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 676d86c..dd95d28 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedMethodImplementation.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedMethodImplementation.java
@@ -129,7 +129,11 @@
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/OatFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java
index dbeb67c..95d61a1 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 @@
// 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;
@@ -149,6 +156,18 @@
}
@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
public List<OatDexFile> getDexFiles() {
return new AbstractForwardSequentialList<OatDexFile>() {
@Override public int size() {
@@ -156,44 +175,44 @@
}
@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);
- }
-
- @Override public void remove() {
- throw new UnsupportedOperationException();
- }
- };
+ });
}
};
}
- public class OatDexFile extends DexBackedDexFile {
+ @Nonnull @Override public List<String> getDexEntryNames() throws IOException {
+ return new AbstractForwardSequentialList<String>() {
+ @Override public int size() {
+ return oatHeader.getDexFileCount();
+ }
+
+ @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;
+ }
+ });
+ }
+ };
+ }
+
+ @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) {
@@ -201,8 +220,12 @@
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 +234,87 @@
}
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 +534,64 @@
return new String(buf, start, end-start, Charset.forName("US-ASCII"));
}
+ }
+ private class DexEntry {
+ public final String entryName;
+ public final int dexOffset;
+
+ public DexEntry(String entryName, int dexOffset) {
+ this.entryName = entryName;
+ this.dexOffset = dexOffset;
+ }
+
+ public OatDexFile getDexFile() {
+ return new OatDexFile(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) + oatHeader.headerOffset;
+ offset += 4;
+
+
+ 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, dexOffset);
+ }
+
+ @Override public void remove() {
+ throw new UnsupportedOperationException();
+ }
}
public static class InvalidOatFileException extends RuntimeException {
@@ -493,4 +603,5 @@
public static class NotAnOatFileException extends RuntimeException {
public NotAnOatFileException() {}
}
+
}
\ 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 0000000..4c0f906
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/ZipDexContainer.java
@@ -0,0 +1,193 @@
+/*
+ * 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 javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.EOFException;
+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;
+
+import static org.jf.dexlib2.dexbacked.DexBackedDexFile.verifyMagicAndByteOrder;
+
+/**
+ * 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;
+ }
+
+ /**
+ * 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() {
+ try {
+ getZipFile();
+ return true;
+ } catch (IOException ex) {
+ return false;
+ } catch (NotAZipFileException ex) {
+ return false;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ private boolean isDex(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry) throws IOException {
+ InputStream inputStream = zipFile.getInputStream(zipEntry);
+ try {
+ inputStream.mark(44);
+ byte[] partialHeader = new byte[44];
+ try {
+ ByteStreams.readFully(inputStream, partialHeader);
+ } catch (EOFException ex) {
+ return false;
+ }
+
+ try {
+ verifyMagicAndByteOrder(partialHeader, 0);
+ } catch (NotADexFile ex) {
+ return false;
+ }
+ return true;
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ private ZipFile getZipFile() throws IOException {
+ try {
+ return new ZipFile(zipFilePath);
+ } catch (IOException ex) {
+ throw new NotAZipFileException();
+ }
+ }
+
+ @Nonnull
+ private 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/iface/MultiDexContainer.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/MultiDexContainer.java
new file mode 100644
index 0000000..251ecde
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/MultiDexContainer.java
@@ -0,0 +1,70 @@
+/*
+ * 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 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;
+
+ /**
+ * 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 2112bd0..76f39a1 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 @@
@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/writer/DexWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java
index 6ca1ce9..5d03d3e 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java
@@ -192,12 +192,12 @@
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 +227,22 @@
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());
}
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 53d1447..8abc776 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 @@
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/builder/BuilderClassPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/builder/BuilderClassPool.java
index 695109f..32b6d35 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
@@ -443,4 +443,8 @@
}
};
}
+
+ @Override public int getItemCount() {
+ return internedItems.size();
+ }
}
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 7a90649..7cb97f2 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
@@ -104,4 +104,8 @@
}
};
}
+
+ @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 2c5dd81..4c5b412 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
@@ -112,6 +112,10 @@
};
}
+ @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 de19fa3..640a548 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
@@ -103,4 +103,8 @@
}
};
}
+
+ @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 6b60e9f..95fe86f 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 @@
}
};
}
+
+ @Override public int getItemCount() {
+ return internedItems.size();
+ }
}
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 29871fc..44ffa80 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
@@ -92,4 +92,8 @@
}
};
}
+
+ @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 b7507fa..cfa7410 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
@@ -60,18 +60,6 @@
@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);
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 01109ad..d7d4afa 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,9 +38,7 @@
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> {
@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/BaseOffsetPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BaseOffsetPool.java
index e66a50a..594a405 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,9 +38,7 @@
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> {
@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 0000000..765896c
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/BasePool.java
@@ -0,0 +1,70 @@
+/*
+ * 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 Map<Key, Value> internedItems = Maps.newLinkedHashMap();
+ private int markedItemCount = -1;
+
+ 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 25cf4da..60df1c9 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,11 +58,9 @@
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;
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 d12457a..c0c2e31 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
@@ -56,16 +56,11 @@
TypeListPool.Key<? extends Collection<? extends CharSequence>>, Field, PoolMethod,
EncodedValue, AnnotationElement> {
- @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[] {
+ (Markable)stringSection, (Markable)typeSection, (Markable)protoSection, (Markable)fieldSection,
+ (Markable)methodSection, (Markable)classSection, (Markable)typeListSection, (Markable)annotationSection,
+ (Markable)annotationSetSection
+ };
@Nonnull
public static DexPool makeDexPool(@Nonnull Opcodes opcodes) {
@@ -84,29 +79,60 @@
annotationPool, annotationSetPool);
}
- private DexPool(Opcodes opcodes, StringPool stringPool, TypePool typePool, ProtoPool protoPool, FieldPool fieldPool,
- MethodPool methodPool, ClassPool classPool, TypeListPool typeListPool,
- AnnotationPool annotationPool, AnnotationSetPool annotationSetPool) {
+ protected 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);
}
- 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 = makeDexPool(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 = makeDexPool(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) {
+ ((ClassPool)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,7 +191,7 @@
}
}
- public static void internEncodedValue(@Nonnull EncodedValue encodedValue,
+ static void internEncodedValue(@Nonnull EncodedValue encodedValue,
@Nonnull StringPool stringPool,
@Nonnull TypePool typePool,
@Nonnull FieldPool fieldPool,
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 0000000..8b14574
--- /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/StringTypeBasePool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/StringTypeBasePool.java
index 768e562..fd4edec 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,9 +40,8 @@
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 {
@Nonnull @Override public Collection<Map.Entry<String, Integer>> getItems() {
return internedItems.entrySet();
}
diff --git a/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java b/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java
index 4c8f85b..ff832c2 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 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 0000000..de5b05f
--- /dev/null
+++ b/dexlib2/src/test/java/org/jf/dexlib2/DexEntryFinderTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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;
+ }
+ }
+}
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 3f1ee56..d69dd81 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 @@
// 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;"),
+ 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;"),
+ // 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;")
- ))));
+ // 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 @@
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 @@
// 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 90a6359..70e6a04 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.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 @@
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));
- ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
- 15, false);
+ ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
+ ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
+ ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
+
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@@ -82,7 +85,7 @@
}
@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 @@
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));
- ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
- 15, false);
+ ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
+ ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
+ ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
+
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@@ -109,7 +114,7 @@
}
@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 @@
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));
- ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
- 15, false);
+ ClassPathResolver resolver = new ClassPathResolver(ImmutableList.<String>of(),
+ ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile);
+ ClassPath classPath = new ClassPath(resolver.getResolvedClassProviders(), false, ClassPath.NOT_ART);
+
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 0000000..2588d58
--- /dev/null
+++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/MethodAnalyzerTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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 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;
+
+public class MethodAnalyzerTest {
+
+ @Test
+ public void testInstanceOfNarrowingEqz() 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.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("Lmain;", analyzedInstructions.get(2).getPreInstructionRegisterType(1).type.getType());
+
+ Assert.assertEquals("Ljava/lang/Object;",
+ analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
+ }
+
+ @Test
+ public void testInstanceOfNarrowingNez() 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("Lmain;", analyzedInstructions.get(3).getPreInstructionRegisterType(1).type.getType());
+ }
+
+ @Test
+ public void testInstanceOfNarrowingAfterMove() 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("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());
+ }
+}
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 84cd284..78bc8a5 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 @@
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 @@
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 0000000..5bc098e
--- /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 = DexPool.makeDexPool(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 = DexPool.makeDexPool(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 1a0a289..bf55e37 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 @@
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 @@
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 c246e0e..8ea30a4 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 @@
public class JumboStringConversionTest {
@Test
public void testJumboStringConversion() throws IOException {
- DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(15));
+ DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.getDefault());
MethodImplementationBuilder methodBuilder = new MethodImplementationBuilder(1);
for (int i=0; i<66000; i++) {
@@ -92,7 +92,7 @@
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 @@
@Test
public void testJumboStringConversion_NonMethodBuilder() throws IOException {
- DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(15));
+ DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.getDefault());
final List<Instruction> instructions = Lists.newArrayList();
for (int i=0; i<66000; i++) {
@@ -189,7 +189,7 @@
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/smali/build.gradle b/smali/build.gradle
index 318b5a9..bf362c4 100644
--- a/smali/build.gradle
+++ b/smali/build.gradle
@@ -76,8 +76,8 @@
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 @@
classifier = 'fat'
manifest {
- attributes('Main-Class': 'org.jf.smali.main')
+ attributes('Main-Class': 'org.jf.smali.Main')
}
doLast {
@@ -141,7 +141,7 @@
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[]); }'
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 29cd141..2d5ecca 100644
--- a/smali/src/main/antlr/smaliParser.g
+++ b/smali/src/main/antlr/smaliParser.g
@@ -263,8 +263,8 @@
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 d074579..171756e 100644
--- a/smali/src/main/antlr/smaliTreeWalker.g
+++ b/smali/src/main/antlr/smaliTreeWalker.g
@@ -85,8 +85,8 @@
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 0000000..efde182
--- /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 0000000..429a7df
--- /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 0000000..6b56fdd
--- /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 0000000..c00da31
--- /dev/null
+++ b/smali/src/main/java/org/jf/smali/Smali.java
@@ -0,0 +1,206 @@
+/*
+ * 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 = DexBuilder.makeDexBuilder(
+ 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 {
+ 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.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;
+ }
+}
diff --git a/smali/src/main/java/org/jf/smali/SmaliOptions.java b/smali/src/main/java/org/jf/smali/SmaliOptions.java
index 165c3a8..ac385fe 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 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 bef0741..b76641b 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 @@
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 = DexBuilder.makeDexBuilder(Opcodes.forApi(apiLevel));
Reader reader = new StringReader(smaliText);
@@ -67,7 +67,7 @@
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 @@
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 @@
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 100755
index 46ad136..0000000
--- a/smali/src/main/java/org/jf/smali/main.java
+++ /dev/null
@@ -1,494 +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 {
- 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.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;
- } finally {
- if (fis != null) {
- fis.close();
- }
- }
- }
-
-
- /**
- * 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/src/main/java/org/jf/smalidea/psi/impl/SmaliInstruction.java b/smalidea/src/main/java/org/jf/smalidea/psi/impl/SmaliInstruction.java
index 8cb2d77..ecbdbb3 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 @@
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/util/build.gradle b/util/build.gradle
index 407ef71..23d6a3a 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 0000000..f457717
--- /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;
+
+ /** > 0; the maximum output width */
+ private final int width;
+
+ /** > 0; the maximum indent */
+ private final int maxIndent;
+
+ /** >= 0; current output column (zero-based) */
+ private int column;
+
+ /** whether indent spaces are currently being collected */
+ private boolean collectingIndent;
+
+ /** >= 0; current indent amount */
+ private int indent;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param out non-null; writer to send final output to
+ * @param width >= 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 >= 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 91eb758..9ba9f30 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 @@
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 @@
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 3d0137e..0000000
--- 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 9180830..304c297 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 @@
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 @@
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 eb1acda..df4575b 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;
+public class WrappedIndentingWriter extends FilterWriter {
- /** > 0; the maximum output width */
- private final int width;
-
- /** > 0; the maximum indent */
private final int maxIndent;
+ private final int maxWidth;
- /** >= 0; current output column (zero-based) */
- private int column;
+ private int currentIndent = 0;
+ private final StringBuilder line = new StringBuilder();
- /** whether indent spaces are currently being collected */
- private boolean collectingIndent;
-
- /** >= 0; current indent amount */
- private int indent;
-
- /**
- * Constructs an instance.
- *
- * @param out non-null; writer to send final output to
- * @param width >= 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);
-
- 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();
+ this.maxIndent = maxIndent;
+ this.maxWidth = maxWidth;
}
- /**
- * Constructs a no-prefix instance.
- *
- * @param out non-null; writer to send final output to
- * @param width >= 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, "");
+ private void writeIndent() throws IOException {
+ for (int i=0; i<getIndent(); i++) {
+ write(' ');
+ }
}
- /** {@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 int getIndent() {
+ if (currentIndent < 0) {
+ return 0;
+ }
+ if (currentIndent > maxIndent) {
+ return maxIndent;
+ }
+ return currentIndent;
+ }
+
+ public void indent(int indent) {
+ currentIndent += indent;
+ }
+
+ public void deindent(int indent) {
+ currentIndent -= indent;
+ }
+
+ 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');
+
+ line.replace(0, line.length(), "");
+ writeIndent();
+ for (int i=1; i<wrapped.size(); i++) {
+ if (i > 1) {
+ write('\n');
}
+ write(wrapped.get(i));
+ }
+ }
- 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;
- }
- }
-
+ @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 0000000..eb628af
--- /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 0000000..8fac0fa
--- /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 0000000..209d94e
--- /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 0000000..81f78c2
--- /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 0000000..965d2b2
--- /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 0000000..e807d5f
--- /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 64dca33..94c7914 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 @@
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));
+ }
+ }
}