aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Murashkin <iam@google.com>2015-04-23 11:31:08 -0700
committerIgor Murashkin <iam@google.com>2015-04-23 11:31:08 -0700
commit07990426302adcf94f2d9595b8066f1257219799 (patch)
treea55d8ef83f6b2b54ee45de8526ec66e230259cb9
parent15284127c9ed241f1de20fdd8cdb24cb12cd2119 (diff)
parent17828564bae2c03788e5366b73ca9e259f70ca5d (diff)
downloadsmali-07990426302adcf94f2d9595b8066f1257219799.tar.gz
Merge remote-tracking branch 'remotes/aosp/upstream-master' into HEAD
* Brings-up smali to 17828564bae2c03788e5366b73ca9e259f70ca5d which has the experimental new opcodes
-rw-r--r--.gitignore4
-rw-r--r--README.md4
-rw-r--r--baksmali/build.gradle45
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java73
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java9
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java9
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java75
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/baksmali.java2
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java1
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/dump.java4
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/main.java23
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java2
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java119
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java104
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java59
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java112
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/LambdaTest.java49
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java42
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java42
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java95
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/SwitchTest.java41
-rw-r--r--baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali1
-rw-r--r--baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali2
-rw-r--r--baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali1
-rw-r--r--baksmali/src/test/resources/LambdaTest/HelloWorldLambda.smali55
-rw-r--r--baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali7
-rw-r--r--baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dexbin0 -> 616 bytes
-rw-r--r--baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali72
-rw-r--r--baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali119
-rw-r--r--baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali35
-rw-r--r--baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali28
-rw-r--r--build.gradle12
-rw-r--r--deodexerant/Android.mk4
-rw-r--r--dexlib2/build.gradle78
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java41
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/Format.java1
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/Opcode.java32
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java5
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java13
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java14
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java14
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java78
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/builder/instruction/BuilderInstruction25x.java82
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java2
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction25x.java83
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java27
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/iface/Annotatable.java49
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/iface/Annotation.java4
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/iface/ClassDef.java4
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/iface/Field.java12
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/iface/Member.java63
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/iface/Method.java6
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/OneFixedFourParameterRegisterInstruction.java47
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/formats/Instruction25x.java37
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java2
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction25x.java97
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java9
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java22
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedValueWriter.java11
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java25
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java (renamed from dexlib2/src/accessorTest/java/org/jf/dexlib2/AccessorTest.java)2
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java6
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java48
-rw-r--r--dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java4
-rw-r--r--examples/HelloWorldLambda/HelloWorldFunctionalInterface.smali8
-rw-r--r--examples/HelloWorldLambda/HelloWorldLambda.smali57
-rw-r--r--gradle.properties3
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin50557 -> 52141 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties4
-rw-r--r--smali/build.gradle144
-rw-r--r--smali/src/main/antlr/smaliParser.g (renamed from smali/src/main/antlr3/smaliParser.g)42
-rw-r--r--smali/src/main/antlr/smaliTreeWalker.g (renamed from smali/src/main/antlr3/smaliTreeWalker.g)81
-rw-r--r--smali/src/main/java/org/jf/smali/SmaliTestUtils.java14
-rw-r--r--smali/src/main/java/org/jf/smali/main.java29
-rw-r--r--smali/src/main/jflex/smaliLexer.jflex (renamed from smali/src/main/jflex/smaliLexer.flex)21
-rw-r--r--smali/src/test/antlr/org/jf/smali/expectedTokensTestGrammar.g (renamed from smali/src/test/antlr3/org/jf/smali/expectedTokensTestGrammar.g)0
-rw-r--r--smali/src/test/java/LexerTest.java3
-rw-r--r--smali/src/test/resources/LexerTest/InstructionTest.smali6
-rw-r--r--smali/src/test/resources/LexerTest/InstructionTest.tokens6
-rw-r--r--util/src/main/java/org/jf/util/ClassFileNameHandler.java25
-rw-r--r--util/src/main/java/org/jf/util/IndentingWriter.java2
-rw-r--r--util/src/main/java/org/jf/util/TextUtils.java27
-rw-r--r--util/src/test/java/org/jf/util/TextUtilsTest.java53
83 files changed, 2264 insertions, 429 deletions
diff --git a/.gitignore b/.gitignore
index 533e4f04..45e097ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,7 @@
/dexlib2/accessorTestGenerator/build
/smali/build
/util/build
+*.iml
+*.ipr
+*.iws
+.idea
diff --git a/README.md b/README.md
index 6d964932..52c24db9 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ The primary webpage is http://smali.googlecode.com, and the source is also mirro
#### Some useful links for getting started with smali
-- [Official dex bytecode reference](http://s.android.com/tech/dalvik/dalvik-bytecode.html)
+- [Official dex bytecode reference](https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html)
- [Registers wiki page](https://code.google.com/p/smali/wiki/Registers)
- [Types, Methods and Fields wiki page](https://code.google.com/p/smali/wiki/TypesMethodsAndFields)
-- [Official dex format reference](http://s.android.com/tech/dalvik/dex-format.html) \ No newline at end of file
+- [Official dex format reference](https://source.android.com/devices/tech/dalvik/dex-format.html)
diff --git a/baksmali/build.gradle b/baksmali/build.gradle
index 150eb6fd..4780cd76 100644
--- a/baksmali/build.gradle
+++ b/baksmali/build.gradle
@@ -29,8 +29,13 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-configurations {
- proguard
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath depends.proguard_gradle
+ }
}
dependencies {
@@ -41,8 +46,6 @@ dependencies {
testCompile depends.junit
testCompile project(':smali')
-
- proguard depends.proguard
}
processResources.inputs.property('version', version)
@@ -59,11 +62,11 @@ task fatJar(type: Jar) {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
manifest {
- attributes("Main-Class": "org.jf.baksmali.main")
+ attributes('Main-Class': 'org.jf.baksmali.main')
}
doLast {
- if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
+ if (!System.getProperty('os.name').toLowerCase().contains('windows')) {
ant.symlink(link: file("${destinationDir}/baksmali.jar"), resource: archivePath, overwrite: true)
}
}
@@ -81,22 +84,22 @@ uploadArchives {
}
}
-task proguard(type: JavaExec, dependsOn: fatJar) {
+task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
def outFile = fatJar.destinationDir.getPath() + '/' + fatJar.baseName + '-' + fatJar.version + '-small' + '.' + fatJar.extension
- inputs.file fatJar.archivePath
- outputs.file outFile
-
- classpath = configurations.proguard
- main = 'proguard.ProGuard'
- args '-injars ' + fatJar.archivePath
- args '-outjars ' + outFile
- args '-libraryjars ' + System.properties['java.home'] + '/lib/rt.jar'
- args '-dontobfuscate'
- args '-dontoptimize'
- args '-keep public class org.jf.baksmali.main { public static void main(java.lang.String[]); }'
- args '-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
- args '-dontwarn com.google.common.**'
- args '-dontnote com.google.common.**'
+
+ injars fatJar.archivePath
+ outjars outFile
+
+ libraryjars "${System.properties['java.home']}/lib/rt.jar"
+
+ dontobfuscate
+ dontoptimize
+
+ 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.**'
+ dontnote 'com.google.common.**'
}
tasks.getByPath(':release').dependsOn(proguard)
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 12acd791..b3f9ae17 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
@@ -41,8 +41,6 @@ import org.jf.dexlib2.iface.instruction.*;
import org.jf.dexlib2.iface.instruction.formats.Instruction20bc;
import org.jf.dexlib2.iface.instruction.formats.Instruction31t;
import org.jf.dexlib2.iface.instruction.formats.UnknownInstruction;
-import org.jf.dexlib2.iface.reference.FieldReference;
-import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.iface.reference.Reference;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.ExceptionWithContext;
@@ -132,26 +130,37 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
}
if (instruction instanceof Instruction31t) {
- Opcode payloadOpcode;
+ boolean validPayload = true;
+
switch (instruction.getOpcode()) {
case PACKED_SWITCH:
- payloadOpcode = Opcode.PACKED_SWITCH_PAYLOAD;
+ int baseAddress = methodDef.getPackedSwitchBaseAddress(
+ this.codeAddress + ((Instruction31t)instruction).getCodeOffset());
+ if (baseAddress == -1) {
+ validPayload = false;
+ }
break;
case SPARSE_SWITCH:
- payloadOpcode = Opcode.SPARSE_SWITCH_PAYLOAD;
+ baseAddress = methodDef.getSparseSwitchBaseAddress(
+ this.codeAddress + ((Instruction31t)instruction).getCodeOffset());
+ if (baseAddress == -1) {
+ validPayload = false;
+ }
break;
case FILL_ARRAY_DATA:
- payloadOpcode = Opcode.ARRAY_PAYLOAD;
+ try {
+ methodDef.findPayloadOffset(this.codeAddress + ((Instruction31t)instruction).getCodeOffset(),
+ Opcode.ARRAY_PAYLOAD);
+ } catch (InvalidSwitchPayload ex) {
+ validPayload = false;
+ }
break;
default:
throw new ExceptionWithContext("Invalid 31t opcode: %s", instruction.getOpcode());
}
- try {
- methodDef.findSwitchPayload(this.codeAddress + ((Instruction31t)instruction).getCodeOffset(),
- payloadOpcode);
- } catch (InvalidSwitchPayload ex) {
- writer.write("#invalid payload reference");
+ if (!validPayload) {
+ writer.write("#invalid payload reference\n");
commentOutInstruction = true;
}
}
@@ -300,6 +309,11 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
writer.write(", ");
writeThirdRegister(writer);
break;
+ case Format25x:
+ writeOpcode(writer);
+ writer.write(' ');
+ writeInvoke25xRegisters(writer); // vC, {vD, ...}
+ break;
case Format35c:
writeOpcode(writer);
writer.write(' ');
@@ -425,6 +439,43 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
writer.write('}');
}
+ protected void writeInvoke25xRegisters(IndentingWriter writer) throws IOException {
+ OneFixedFourParameterRegisterInstruction instruction =
+ (OneFixedFourParameterRegisterInstruction)this.instruction;
+ final int parameterRegCount = instruction.getParameterRegisterCount();
+
+ writeRegister(writer, instruction.getRegisterFixedC()); // fixed register always present
+
+ writer.write(", {");
+ switch (parameterRegCount) {
+ case 1:
+ writeRegister(writer, instruction.getRegisterParameterD());
+ break;
+ case 2:
+ writeRegister(writer, instruction.getRegisterParameterD());
+ writer.write(", ");
+ writeRegister(writer, instruction.getRegisterParameterE());
+ break;
+ case 3:
+ writeRegister(writer, instruction.getRegisterParameterD());
+ writer.write(", ");
+ writeRegister(writer, instruction.getRegisterParameterE());
+ writer.write(", ");
+ writeRegister(writer, instruction.getRegisterParameterF());
+ break;
+ case 4:
+ writeRegister(writer, instruction.getRegisterParameterD());
+ writer.write(", ");
+ writeRegister(writer, instruction.getRegisterParameterE());
+ writer.write(", ");
+ writeRegister(writer, instruction.getRegisterParameterF());
+ writer.write(", ");
+ writeRegister(writer, instruction.getRegisterParameterG());
+ break;
+ }
+ writer.write('}');
+ }
+
protected void writeInvokeRangeRegisters(IndentingWriter writer) throws IOException {
RegisterRangeInstruction instruction = (RegisterRangeInstruction)this.instruction;
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java
index f0dd656b..30edfcd4 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java
@@ -28,6 +28,7 @@
package org.jf.baksmali.Adaptors.Format;
+import org.jf.baksmali.Adaptors.CommentingIndentingWriter;
import org.jf.baksmali.Adaptors.LabelMethodItem;
import org.jf.baksmali.Adaptors.MethodDefinition;
import org.jf.dexlib2.iface.instruction.SwitchElement;
@@ -43,6 +44,9 @@ public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchPa
private final List<PackedSwitchTarget> targets;
private final int firstKey;
+ // Whether this sparse switch instruction should be commented out because it is never referenced
+ private boolean commentedOut;
+
public PackedSwitchMethodItem(MethodDefinition methodDef, int codeAddress, PackedSwitchPayload instruction) {
super(methodDef, codeAddress, instruction);
@@ -51,7 +55,6 @@ public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchPa
targets = new ArrayList<PackedSwitchTarget>();
boolean first = true;
- //TODO: does dalvik allow switc payloads with no cases?
int firstKey = 0;
if (baseCodeAddress >= 0) {
for (SwitchElement switchElement: instruction.getSwitchElements()) {
@@ -65,6 +68,7 @@ public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchPa
targets.add(new PackedSwitchLabelTarget(label));
}
} else {
+ commentedOut = true;
for (SwitchElement switchElement: instruction.getSwitchElements()) {
if (first) {
firstKey = switchElement.getKey();
@@ -78,6 +82,9 @@ public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchPa
@Override
public boolean writeTo(IndentingWriter writer) throws IOException {
+ if (commentedOut) {
+ writer = new CommentingIndentingWriter(writer);
+ }
writer.write(".packed-switch ");
IntegerRenderer.writeTo(writer, firstKey);
writer.indent(4);
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java
index 0e01f41b..68d7e92b 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java
@@ -28,6 +28,7 @@
package org.jf.baksmali.Adaptors.Format;
+import org.jf.baksmali.Adaptors.CommentingIndentingWriter;
import org.jf.baksmali.Adaptors.LabelMethodItem;
import org.jf.baksmali.Adaptors.MethodDefinition;
import org.jf.dexlib2.iface.instruction.SwitchElement;
@@ -42,6 +43,9 @@ import java.util.List;
public class SparseSwitchMethodItem extends InstructionMethodItem<SparseSwitchPayload> {
private final List<SparseSwitchTarget> targets;
+ // Whether this sparse switch instruction should be commented out because it is never referenced
+ private boolean commentedOut;
+
public SparseSwitchMethodItem(MethodDefinition methodDef, int codeAddress, SparseSwitchPayload instruction) {
super(methodDef, codeAddress, instruction);
@@ -56,6 +60,7 @@ public class SparseSwitchMethodItem extends InstructionMethodItem<SparseSwitchPa
targets.add(new SparseSwitchLabelTarget(switchElement.getKey(), label));
}
} else {
+ commentedOut = true;
//if we couldn't determine a base address, just use relative offsets rather than labels
for (SwitchElement switchElement: instruction.getSwitchElements()) {
targets.add(new SparseSwitchOffsetTarget(switchElement.getKey(), switchElement.getOffset()));
@@ -65,6 +70,10 @@ public class SparseSwitchMethodItem extends InstructionMethodItem<SparseSwitchPa
@Override
public boolean writeTo(IndentingWriter writer) throws IOException {
+ if (commentedOut) {
+ writer = new CommentingIndentingWriter(writer);
+ }
+
writer.write(".sparse-switch\n");
writer.indent(4);
for (SparseSwitchTarget target: targets) {
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 eb5caa67..4081a75c 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
@@ -29,6 +29,7 @@
package org.jf.baksmali.Adaptors;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
import org.jf.baksmali.Adaptors.Debug.DebugMethodItem;
import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory;
import org.jf.baksmali.baksmaliOptions;
@@ -45,11 +46,14 @@ import org.jf.dexlib2.iface.debug.DebugItem;
import org.jf.dexlib2.iface.instruction.Instruction;
import org.jf.dexlib2.iface.instruction.OffsetInstruction;
import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
+import org.jf.dexlib2.iface.instruction.formats.Instruction31t;
import org.jf.dexlib2.iface.reference.MethodReference;
+import org.jf.dexlib2.immutable.instruction.ImmutableInstruction31t;
import org.jf.dexlib2.util.InstructionOffsetMap;
import org.jf.dexlib2.util.InstructionOffsetMap.InvalidInstructionOffset;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.dexlib2.util.SyntheticAccessorResolver;
+import org.jf.dexlib2.util.SyntheticAccessorResolver.AccessedMember;
import org.jf.dexlib2.util.TypeUtils;
import org.jf.util.ExceptionWithContext;
import org.jf.util.IndentingWriter;
@@ -65,6 +69,8 @@ public class MethodDefinition {
@Nonnull public final Method method;
@Nonnull public final MethodImplementation methodImpl;
@Nonnull public final ImmutableList<Instruction> instructions;
+ @Nonnull public final List<Instruction> effectiveInstructions;
+
@Nonnull public final ImmutableList<MethodParameter> methodParameters;
public RegisterFormatter registerFormatter;
@@ -86,10 +92,15 @@ public class MethodDefinition {
instructions = ImmutableList.copyOf(methodImpl.getInstructions());
methodParameters = ImmutableList.copyOf(method.getParameters());
+ effectiveInstructions = Lists.newArrayList(instructions);
+
packedSwitchMap = new SparseIntArray(0);
sparseSwitchMap = new SparseIntArray(0);
instructionOffsetMap = new InstructionOffsetMap(instructions);
+ int endOffset = instructionOffsetMap.getInstructionCodeOffset(instructions.size()-1) +
+ instructions.get(instructions.size()-1).getCodeUnits();
+
for (int i=0; i<instructions.size(); i++) {
Instruction instruction = instructions.get(i);
@@ -99,11 +110,20 @@ public class MethodDefinition {
int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
try {
- targetOffset = findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
+ targetOffset = findPayloadOffset(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
} catch (InvalidSwitchPayload ex) {
valid = false;
}
if (valid) {
+ if (packedSwitchMap.get(targetOffset, -1) != -1) {
+ Instruction payloadInstruction =
+ findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
+ targetOffset = endOffset;
+ effectiveInstructions.set(i, new ImmutableInstruction31t(opcode,
+ ((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset));
+ effectiveInstructions.add(payloadInstruction);
+ endOffset += payloadInstruction.getCodeUnits();
+ }
packedSwitchMap.append(targetOffset, codeOffset);
}
} else if (opcode == Opcode.SPARSE_SWITCH) {
@@ -111,18 +131,27 @@ public class MethodDefinition {
int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
try {
- targetOffset = findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
+ targetOffset = findPayloadOffset(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
} catch (InvalidSwitchPayload ex) {
valid = false;
// The offset to the payload instruction was invalid. Nothing to do, except that we won't
// add this instruction to the map.
}
if (valid) {
+ if (sparseSwitchMap.get(targetOffset, -1) != -1) {
+ Instruction payloadInstruction =
+ findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
+ targetOffset = endOffset;
+ effectiveInstructions.set(i, new ImmutableInstruction31t(opcode,
+ ((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset));
+ effectiveInstructions.add(payloadInstruction);
+ endOffset += payloadInstruction.getCodeUnits();
+ }
sparseSwitchMap.append(targetOffset, codeOffset);
}
}
}
- }catch (Exception ex) {
+ } catch (Exception ex) {
String methodString;
try {
methodString = ReferenceUtil.getMethodDescriptor(method);
@@ -216,7 +245,36 @@ public class MethodDefinition {
writer.write(".end method\n");
}
- public int findSwitchPayload(int targetOffset, Opcode type) {
+ public Instruction findSwitchPayload(int targetOffset, Opcode type) {
+ int targetIndex;
+ try {
+ targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset);
+ } catch (InvalidInstructionOffset ex) {
+ throw new InvalidSwitchPayload(targetOffset);
+ }
+
+ //TODO: does dalvik let you pad with multiple nops?
+ //TODO: does dalvik let a switch instruction point to a non-payload instruction?
+
+ Instruction instruction = instructions.get(targetIndex);
+ if (instruction.getOpcode() != type) {
+ // maybe it's pointing to a NOP padding instruction. Look at the next instruction
+ if (instruction.getOpcode() == Opcode.NOP) {
+ targetIndex += 1;
+ if (targetIndex < instructions.size()) {
+ instruction = instructions.get(targetIndex);
+ if (instruction.getOpcode() == type) {
+ return instruction;
+ }
+ }
+ }
+ throw new InvalidSwitchPayload(targetOffset);
+ } else {
+ return instruction;
+ }
+ }
+
+ public int findPayloadOffset(int targetOffset, Opcode type) {
int targetIndex;
try {
targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset);
@@ -343,15 +401,16 @@ public class MethodDefinition {
private void addInstructionMethodItems(List<MethodItem> methodItems) {
int currentCodeAddress = 0;
- for (int i=0; i<instructions.size(); i++) {
- Instruction instruction = instructions.get(i);
+
+ for (int i=0; i<effectiveInstructions.size(); i++) {
+ Instruction instruction = effectiveInstructions.get(i);
MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
currentCodeAddress, instruction);
methodItems.add(methodItem);
- if (i != instructions.size() - 1) {
+ if (i != effectiveInstructions.size() - 1) {
methodItems.add(new BlankMethodItem(currentCodeAddress));
}
@@ -386,7 +445,7 @@ public class MethodDefinition {
if (methodReference != null &&
SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodReference.getName())) {
- SyntheticAccessorResolver.AccessedMember accessedMember =
+ AccessedMember accessedMember =
classDef.options.syntheticAccessorResolver.getAccessedMember(methodReference);
if (accessedMember != null) {
methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress));
diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java
index 765e77ec..47fa406d 100644
--- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java
+++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java
@@ -67,7 +67,7 @@ public class baksmali {
options.classPath = ClassPath.fromClassPath(options.bootClassPathDirs,
Iterables.concat(options.bootClassPathEntries, extraClassPathEntries), dexFile,
- options.apiLevel, options.checkPackagePrivateAccess);
+ options.apiLevel, options.checkPackagePrivateAccess, options.experimental);
if (options.customInlineDefinitions != null) {
options.inlineResolver = new CustomInlineMethodResolver(options.classPath,
diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java
index ada28239..b6cc1571 100644
--- a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java
+++ b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java
@@ -71,6 +71,7 @@ public class baksmaliOptions {
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;
diff --git a/baksmali/src/main/java/org/jf/baksmali/dump.java b/baksmali/src/main/java/org/jf/baksmali/dump.java
index bd040e6d..1ef7df0e 100644
--- a/baksmali/src/main/java/org/jf/baksmali/dump.java
+++ b/baksmali/src/main/java/org/jf/baksmali/dump.java
@@ -40,7 +40,7 @@ import java.io.IOException;
import java.io.Writer;
public class dump {
- public static void dump(DexBackedDexFile dexFile, String dumpFileName, int apiLevel) throws IOException {
+ public static void dump(DexBackedDexFile dexFile, String dumpFileName, int apiLevel, boolean experimental) throws IOException {
if (dumpFileName != null) {
Writer writer = null;
@@ -52,7 +52,7 @@ public class dump {
consoleWidth = 120;
}
- RawDexFile rawDexFile = new RawDexFile(new Opcodes(apiLevel), dexFile);
+ RawDexFile rawDexFile = new RawDexFile(new Opcodes(apiLevel, experimental), dexFile);
DexAnnotator annotator = new DexAnnotator(rawDexFile, consoleWidth);
annotator.writeAnnotations(writer);
} catch (IOException ex) {
diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java
index 28c42030..71598fa6 100644
--- a/baksmali/src/main/java/org/jf/baksmali/main.java
+++ b/baksmali/src/main/java/org/jf/baksmali/main.java
@@ -192,6 +192,9 @@ public class main {
case 'x':
options.deodex = true;
break;
+ case 'X':
+ options.experimental = true;
+ break;
case 'm':
options.noAccessorComments = true;
break;
@@ -253,7 +256,8 @@ public class main {
}
//Read in and parse the dex file
- DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry, options.apiLevel);
+ DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry,
+ options.apiLevel, options.experimental);
if (dexFile.isOdexFile()) {
if (!options.deodex) {
@@ -270,13 +274,15 @@ public class main {
if (dexFile instanceof DexBackedOdexFile) {
options.bootClassPathEntries = ((DexBackedOdexFile)dexFile).getDependencies();
} else {
- options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel);
+ options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel,
+ options.experimental);
}
}
if (options.customInlineDefinitions == null && dexFile instanceof DexBackedOdexFile) {
options.inlineResolver =
- InlineMethodResolver.createInlineMethodResolver(((DexBackedOdexFile)dexFile).getOdexVersion());
+ InlineMethodResolver.createInlineMethodResolver(
+ ((DexBackedOdexFile)dexFile).getOdexVersion());
}
boolean errorOccurred = false;
@@ -288,7 +294,7 @@ public class main {
if (dumpFileName == null) {
dumpFileName = commandLine.getOptionValue(inputDexFileName + ".dump");
}
- dump.dump(dexFile, dumpFileName, options.apiLevel);
+ dump.dump(dexFile, dumpFileName, options.apiLevel, options.experimental);
}
if (errorOccurred) {
@@ -352,6 +358,10 @@ public class main {
"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")
@@ -469,6 +479,7 @@ public class main {
basicOptions.addOption(outputDirOption);
basicOptions.addOption(noParameterRegistersOption);
basicOptions.addOption(deodexerantOption);
+ basicOptions.addOption(experimentalOption);
basicOptions.addOption(useLocalsOption);
basicOptions.addOption(sequentialLabelsOption);
basicOptions.addOption(noDebugInfoOption);
@@ -496,9 +507,9 @@ public class main {
options.addOption((Option)option);
}
}
-
+
@Nonnull
- private static List<String> getDefaultBootClassPathForApi(int apiLevel) {
+ private static List<String> getDefaultBootClassPathForApi(int apiLevel, boolean experimental) {
if (apiLevel < 9) {
return Lists.newArrayList(
"/system/framework/core.jar",
diff --git a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
index bf353e83..725032af 100644
--- a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
@@ -85,7 +85,7 @@ public class AnalysisTest {
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);
+ DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false);
baksmaliOptions options = new baksmaliOptions();
if (registerInfo) {
diff --git a/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java
new file mode 100644
index 00000000..1c570b6c
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2015, 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.io.ByteStreams;
+import junit.framework.Assert;
+
+import org.antlr.runtime.RecognitionException;
+import org.jf.baksmali.Adaptors.ClassDefinition;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.smali.SmaliTestUtils;
+import org.jf.util.IndentingWriter;
+import org.jf.util.TextUtils;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+
+public class BaksmaliTestUtils {
+ public static void assertSmaliCompiledEquals(String source, String expected,
+ baksmaliOptions options, boolean stripComments) throws IOException,
+ RecognitionException {
+ ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel,
+ options.experimental);
+
+ // Remove unnecessary whitespace and optionally strip all comments from smali file
+ String normalizedActual = getNormalizedSmali(classDef, options, stripComments);
+ String normalizedExpected = normalizeSmali(expected, stripComments);
+
+ // Assert that normalized strings are now equal
+ Assert.assertEquals(normalizedExpected, normalizedActual);
+ }
+
+ public static void assertSmaliCompiledEquals(String source, String expected,
+ 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();
+ assertSmaliCompiledEquals(source, expected, options);
+ }
+
+ @Nonnull
+ public static String normalizeSmali(@Nonnull String smaliText, boolean stripComments) {
+ if (stripComments) {
+ smaliText = TextUtils.stripComments(smaliText);
+ }
+ return TextUtils.normalizeWhitespace(smaliText);
+ }
+
+ @Nonnull
+ public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull baksmaliOptions options,
+ boolean stripComments)
+ throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ IndentingWriter writer = new IndentingWriter(stringWriter);
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+ classDefinition.writeTo(writer);
+ writer.close();
+ return normalizeSmali(stringWriter.toString(), stripComments);
+ }
+
+ @Nonnull
+ public static byte[] readResourceBytesFully(@Nonnull String fileName) throws IOException {
+ InputStream smaliStream = RoundtripTest.class.getClassLoader().
+ getResourceAsStream(fileName);
+ if (smaliStream == null) {
+ org.junit.Assert.fail("Could not load " + fileName);
+ }
+
+ return ByteStreams.toByteArray(smaliStream);
+ }
+
+ @Nonnull
+ public static String readResourceFully(@Nonnull String fileName) throws IOException {
+ return readResourceFully(fileName, "UTF-8");
+ }
+
+ @Nonnull
+ public static String readResourceFully(@Nonnull String fileName, @Nonnull String encoding)
+ throws IOException {
+ return new String(readResourceBytesFully(fileName), encoding);
+ }
+
+ // Static helpers class; do not instantiate.
+ private BaksmaliTestUtils() { throw new AssertionError(); }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java
new file mode 100644
index 00000000..35304f7e
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015, 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.Iterables;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.iface.ClassDef;
+import org.junit.Assert;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A base test class for performing a disassembly on a dex file and verifying the results
+ *
+ * The test accepts a single-class dex file as input, disassembles it, and verifies that
+ * the result equals a known-good output smali file.
+ *
+ * By default, the input and output files should be resources at [testDir]/[testName]Input.dex
+ * and [testDir]/[testName]Output.smali respectively
+ */
+public class DisassemblyTest {
+ protected final String testDir;
+
+ protected DisassemblyTest(@Nonnull String testDir) {
+ this.testDir = testDir;
+ }
+
+ protected DisassemblyTest() {
+ this.testDir = this.getClass().getSimpleName();
+ }
+
+ @Nonnull
+ protected String getInputFilename(@Nonnull String testName) {
+ return String.format("%s%s%sInput.dex", testDir, File.separatorChar, testName);
+ }
+
+ @Nonnull
+ protected String getOutputFilename(@Nonnull String testName) {
+ return String.format("%s%s%sOutput.smali", testDir, File.separatorChar, testName);
+ }
+
+ protected void runTest(@Nonnull String testName) {
+ runTest(testName, new baksmaliOptions());
+ }
+
+ protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) {
+ try {
+ // Load file from resources as a stream
+ String inputFilename = getInputFilename(testName);
+ byte[] inputBytes = BaksmaliTestUtils.readResourceBytesFully(getInputFilename(testName));
+
+ DexBackedDexFile inputDex = new DexBackedDexFile(new Opcodes(options.apiLevel, false), inputBytes);
+ Assert.assertEquals(1, inputDex.getClassCount());
+ ClassDef inputClass = Iterables.getFirst(inputDex.getClasses(), null);
+ Assert.assertNotNull(inputClass);
+ String input = BaksmaliTestUtils.getNormalizedSmali(inputClass, options, true);
+
+ String output;
+ if (getOutputFilename(testName).equals(inputFilename)) {
+ output = input;
+ } else {
+ output = BaksmaliTestUtils.readResourceFully(getOutputFilename(testName));
+ }
+ output = BaksmaliTestUtils.normalizeSmali(output, true);
+
+ // Run smali, baksmali, and then compare strings are equal (minus comments/whitespace)
+ Assert.assertEquals(output, input);
+ } catch (IOException ex) {
+ Assert.fail();
+ }
+ }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java
new file mode 100644
index 00000000..e636ee1e
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015, 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 javax.annotation.Nonnull;
+import java.io.File;
+
+/**
+ * A base test class for performing a roundtrip assembly/disassembly where the input and output
+ * should be identical.
+ *
+ * By default, the input/output file should be a resource at [testDir]/[testName].smali
+ */
+public abstract class IdenticalRoundtripTest extends RoundtripTest {
+
+ public IdenticalRoundtripTest(@Nonnull String testDir) {
+ super(testDir);
+ }
+
+ public IdenticalRoundtripTest() {
+ }
+
+ @Nonnull @Override protected String getInputFilename(@Nonnull String testName) {
+ return String.format("%s%s%s.smali", testDir, File.separatorChar, testName);
+ }
+
+ @Nonnull @Override protected String getOutputFilename(@Nonnull String testName) {
+ return getInputFilename(testName);
+ }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
index 0cda27a1..1f2ae5bf 100644
--- a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
@@ -31,22 +31,15 @@
package org.jf.baksmali;
-import junit.framework.Assert;
import org.antlr.runtime.RecognitionException;
-import org.jf.baksmali.Adaptors.ClassDefinition;
-import org.jf.dexlib2.iface.ClassDef;
-import org.jf.smali.SmaliTestUtils;
-import org.jf.util.IndentingWriter;
-import org.jf.util.TextUtils;
import org.junit.Test;
import java.io.IOException;
-import java.io.StringWriter;
public class ImplicitReferenceTest {
@Test
public void testImplicitMethodReferences() throws IOException, RecognitionException {
- ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ String source = "" +
".class public LHelloWorld;\n" +
".super Ljava/lang/Object;\n" +
".method public static main([Ljava/lang/String;)V\n" +
@@ -55,7 +48,7 @@ public class ImplicitReferenceTest {
" invoke-static {p0}, LHelloWorld;->V()V\n" +
" invoke-static {p0}, LHelloWorld;->I()V\n" +
" return-void\n" +
- ".end method");
+ ".end method";
String expected = "" +
".class public LHelloWorld;\n" +
@@ -72,19 +65,12 @@ public class ImplicitReferenceTest {
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = true;
- StringWriter stringWriter = new StringWriter();
- IndentingWriter writer = new IndentingWriter(stringWriter);
- ClassDefinition classDefinition = new ClassDefinition(options, classDef);
- classDefinition.writeTo(writer);
- writer.close();
-
- Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
- TextUtils.normalizeWhitespace(stringWriter.toString()));
+ BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@Test
public void testExplicitMethodReferences() throws IOException, RecognitionException {
- ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ String source = "" +
".class public LHelloWorld;\n" +
".super Ljava/lang/Object;\n" +
".method public static main([Ljava/lang/String;)V\n" +
@@ -93,7 +79,7 @@ public class ImplicitReferenceTest {
" invoke-static {p0}, LHelloWorld;->V()V\n" +
" invoke-static {p0}, LHelloWorld;->I()V\n" +
" return-void\n" +
- ".end method");
+ ".end method";
String expected = "" +
".class public LHelloWorld;\n" +
@@ -110,25 +96,18 @@ public class ImplicitReferenceTest {
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = false;
- StringWriter stringWriter = new StringWriter();
- IndentingWriter writer = new IndentingWriter(stringWriter);
- ClassDefinition classDefinition = new ClassDefinition(options, classDef);
- classDefinition.writeTo(writer);
- writer.close();
-
- Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
- TextUtils.normalizeWhitespace(stringWriter.toString()));
+ BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@Test
public void testImplicitMethodLiterals() throws IOException, RecognitionException {
- ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ String source = "" +
".class public LHelloWorld;\n" +
".super Ljava/lang/Object;\n" +
".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" +
".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" +
".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
- ".field public static field4:Ljava/lang/Class; = I");
+ ".field public static field4:Ljava/lang/Class; = I";
String expected = "" +
".class public LHelloWorld;\n" +
@@ -142,25 +121,18 @@ public class ImplicitReferenceTest {
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = true;
- StringWriter stringWriter = new StringWriter();
- IndentingWriter writer = new IndentingWriter(stringWriter);
- ClassDefinition classDefinition = new ClassDefinition(options, classDef);
- classDefinition.writeTo(writer);
- writer.close();
-
- Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
- TextUtils.normalizeWhitespace(stringWriter.toString()));
+ BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@Test
public void testExplicitMethodLiterals() throws IOException, RecognitionException {
- ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ String source = "" +
".class public LHelloWorld;\n" +
".super Ljava/lang/Object;\n" +
".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" +
".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" +
".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
- ".field public static field4:Ljava/lang/Class; = I");
+ ".field public static field4:Ljava/lang/Class; = I";
String expected = "" +
".class public LHelloWorld;\n" +
@@ -174,19 +146,12 @@ public class ImplicitReferenceTest {
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = false;
- StringWriter stringWriter = new StringWriter();
- IndentingWriter writer = new IndentingWriter(stringWriter);
- ClassDefinition classDefinition = new ClassDefinition(options, classDef);
- classDefinition.writeTo(writer);
- writer.close();
-
- Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
- TextUtils.normalizeWhitespace(stringWriter.toString()));
+ BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@Test
public void testImplicitFieldReferences() throws IOException, RecognitionException {
- ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ String source = "" +
".class public LHelloWorld;\n" +
".super Ljava/lang/Object;\n" +
".method public static main([Ljava/lang/String;)V\n" +
@@ -195,7 +160,7 @@ public class ImplicitReferenceTest {
" sget v0, LHelloWorld;->I:I\n" +
" sget v0, LHelloWorld;->V:I\n" +
" return-void\n" +
- ".end method");
+ ".end method";
String expected = "" +
".class public LHelloWorld;\n" +
@@ -212,19 +177,12 @@ public class ImplicitReferenceTest {
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = true;
- StringWriter stringWriter = new StringWriter();
- IndentingWriter writer = new IndentingWriter(stringWriter);
- ClassDefinition classDefinition = new ClassDefinition(options, classDef);
- classDefinition.writeTo(writer);
- writer.close();
-
- Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
- TextUtils.normalizeWhitespace(stringWriter.toString()));
+ BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@Test
public void testExplicitFieldReferences() throws IOException, RecognitionException {
- ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ String source = "" +
".class public LHelloWorld;\n" +
".super Ljava/lang/Object;\n" +
".method public static main([Ljava/lang/String;)V\n" +
@@ -233,7 +191,7 @@ public class ImplicitReferenceTest {
" sget v0, LHelloWorld;->I:I\n" +
" sget v0, LHelloWorld;->V:I\n" +
" return-void\n" +
- ".end method");
+ ".end method";
String expected = "" +
".class public LHelloWorld;\n" +
@@ -250,24 +208,17 @@ public class ImplicitReferenceTest {
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = false;
- StringWriter stringWriter = new StringWriter();
- IndentingWriter writer = new IndentingWriter(stringWriter);
- ClassDefinition classDefinition = new ClassDefinition(options, classDef);
- classDefinition.writeTo(writer);
- writer.close();
-
- Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
- TextUtils.normalizeWhitespace(stringWriter.toString()));
+ BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@Test
public void testImplicitFieldLiterals() throws IOException, RecognitionException {
- ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ String source = "" +
".class public LHelloWorld;\n" +
".super Ljava/lang/Object;\n" +
".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" +
".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
- ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I");
+ ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I";
String expected = "" +
".class public LHelloWorld;\n" +
@@ -280,24 +231,17 @@ public class ImplicitReferenceTest {
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = true;
- StringWriter stringWriter = new StringWriter();
- IndentingWriter writer = new IndentingWriter(stringWriter);
- ClassDefinition classDefinition = new ClassDefinition(options, classDef);
- classDefinition.writeTo(writer);
- writer.close();
-
- Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
- TextUtils.normalizeWhitespace(stringWriter.toString()));
+ BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
@Test
public void testExplicitFieldLiterals() throws IOException, RecognitionException {
- ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ String source = "" +
".class public LHelloWorld;\n" +
".super Ljava/lang/Object;\n" +
".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" +
".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
- ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I");
+ ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I";
String expected = "" +
".class public LHelloWorld;\n" +
@@ -310,13 +254,7 @@ public class ImplicitReferenceTest {
baksmaliOptions options = new baksmaliOptions();
options.useImplicitReferences = false;
- StringWriter stringWriter = new StringWriter();
- IndentingWriter writer = new IndentingWriter(stringWriter);
- ClassDefinition classDefinition = new ClassDefinition(options, classDef);
- classDefinition.writeTo(writer);
- writer.close();
-
- Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
- TextUtils.normalizeWhitespace(stringWriter.toString()));
+ BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options);
}
+
}
diff --git a/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java b/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java
new file mode 100644
index 00000000..5431df54
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015, 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.junit.Test;
+
+public class LambdaTest extends IdenticalRoundtripTest {
+
+ private baksmaliOptions createOptions() {
+ baksmaliOptions options = new baksmaliOptions();
+ options.apiLevel = 23; // since we need at least level 23 for lambda opcodes
+ options.experimental = true; // since these opcodes aren't implemented in runtime yet);
+ return options;
+ }
+
+ @Test
+ public void testHelloWorldLambda() {
+ runTest("HelloWorldLambda", createOptions());
+ }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java b/baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java
new file mode 100644
index 00000000..5a267161
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015, 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.junit.Test;
+
+public class ManyRegistersTest extends IdenticalRoundtripTest {
+
+ @Test
+ public void testManyRegisters() {
+ runTest("ManyRegisters");
+ }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java b/baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java
new file mode 100644
index 00000000..cb29402f
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015, 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.junit.Test;
+
+public class MultiSwitchTest extends DisassemblyTest {
+
+ @Test
+ public void testMultiSwitch() {
+ runTest("MultiSwitch");
+ }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java
new file mode 100644
index 00000000..c9ff2d4d
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015, 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.antlr.runtime.RecognitionException;
+import org.junit.Assert;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A base test class for performing a roundtrip assembly/disassembly
+ *
+ * The test accepts a smali file as input, performs a smali -> dex -> smali roundtrip, and
+ * verifies that the result equals a known-good output smali file.
+ *
+ * By default, the input and output files should be resources at [testDir]/[testName]Input.smali
+ * and [testDir]/[testName]Output.smali respectively
+ */
+public abstract class RoundtripTest {
+ protected final String testDir;
+
+ protected RoundtripTest(@Nonnull String testDir) {
+ this.testDir = testDir;
+ }
+
+ protected RoundtripTest() {
+ this.testDir = this.getClass().getSimpleName();
+ }
+
+ @Nonnull
+ protected String getInputFilename(@Nonnull String testName) {
+ return String.format("%s%s%sInput.smali", testDir, File.separatorChar, testName);
+ }
+
+ @Nonnull
+ protected String getOutputFilename(@Nonnull String testName) {
+ return String.format("%s%s%sOutput.smali", testDir, File.separatorChar, testName);
+ }
+
+ protected void runTest(@Nonnull String testName) {
+ runTest(testName, new baksmaliOptions());
+ }
+
+ protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) {
+ try {
+ // Load file from resources as a stream
+ String inputFilename = getInputFilename(testName);
+ String input = BaksmaliTestUtils.readResourceFully(getInputFilename(testName));
+ String output;
+ if (getOutputFilename(testName).equals(inputFilename)) {
+ output = input;
+ } else {
+ output = BaksmaliTestUtils.readResourceFully(getOutputFilename(testName));
+ }
+
+ // Run smali, baksmali, and then compare strings are equal (minus comments/whitespace)
+ BaksmaliTestUtils.assertSmaliCompiledEquals(input, output, options, true);
+ } catch (IOException ex) {
+ Assert.fail();
+ } catch (RecognitionException ex) {
+ Assert.fail();
+ }
+ }
+}
diff --git a/baksmali/src/test/java/org/jf/baksmali/SwitchTest.java b/baksmali/src/test/java/org/jf/baksmali/SwitchTest.java
new file mode 100644
index 00000000..48b64b22
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/SwitchTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015, 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.junit.Test;
+
+public class SwitchTest extends RoundtripTest {
+ @Test
+ public void testUnorderedSparseSwitch() {
+ runTest("UnorderedSparseSwitch");
+ }
+}
diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali
index b1f30c9a..fd43b021 100644
--- a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali
+++ b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali
@@ -22,7 +22,6 @@
# return-void
# .end method
-
.method private clah()V
.registers 1
diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali
index 8d87c1d8..52865737 100644
--- a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali
+++ b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali
@@ -17,7 +17,6 @@
# .end method
-
# virtual methods
.method public alah()V
.registers 1
@@ -40,7 +39,6 @@
# return-void
# .end method
-
.method public clah()V
.registers 1
diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali
index 74af4c58..3c080024 100644
--- a/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali
+++ b/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali
@@ -22,7 +22,6 @@
# return-void
# .end method
-
.method public clah()V
.registers 1
diff --git a/baksmali/src/test/resources/LambdaTest/HelloWorldLambda.smali b/baksmali/src/test/resources/LambdaTest/HelloWorldLambda.smali
new file mode 100644
index 00000000..d70ced50
--- /dev/null
+++ b/baksmali/src/test/resources/LambdaTest/HelloWorldLambda.smali
@@ -0,0 +1,55 @@
+.class public LHelloWorldLambda;
+
+#Ye olde hello world application (with lambdas!)
+#To assemble and run this on a phone or emulator:
+#
+#java -jar smali.jar -o classes.dex HelloWorldLambda.smali HelloWorldFunctionalInterface.smali
+#zip HelloWorld.zip classes.dex
+#adb push HelloWorld.zip /data/local
+#adb shell dalvikvm -cp /data/local/HelloWorld.zip HelloWorld
+#
+#if you get out of memory type errors when running smali.jar, try
+#java -Xmx512m -jar smali.jar HelloWorldLambda.smali
+#instead
+
+.super Ljava/lang/Object;
+
+.method public static doHelloWorld(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ .registers 6 # 4 parameters, 2 locals
+ liberate-variable v0, p0, "helloworld"
+
+ sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
+ invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+ return-void
+.end method
+
+.method public static main([Ljava/lang/String;)V
+ .registers 9 # 1 parameter, 8 locals
+
+ sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+ const-string v1, "Hello World!"
+ const-string v2, "How" # vD
+ const-string v3, "are" # vE
+ const-string v4, "you" # vF
+ const-string v5, "doing?" # vG
+
+ capture-variable v1, "helloworld"
+
+ # TODO: do I need to pass the type of the lambda's functional interface here as a type id?
+ create-lambda v1, LHelloWorldLambda;->doHelloWorld(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ # Method descriptor is not required here, because only the single-abstract method is ever invoked.
+ invoke-lambda v1, {v2, v3, v4, v5}
+
+ box-lambda v6, v1
+ invoke-virtual {v6, v2, v3, v4, v5}, LHelloWorldFunctionalInterface;->applyFourStrings(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+
+ # FIXME: should be \HelloWorldFunctionalInterface; instead of L...;
+
+ # TODO: do we really need the type descriptor here at all?
+ unbox-lambda v7, v6, LHelloWorldFunctionalInterface;
+ invoke-lambda v7, {v2, v3, v4, v5}
+
+ return-void
+.end method
diff --git a/baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali b/baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali
new file mode 100644
index 00000000..7f7c7bb1
--- /dev/null
+++ b/baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali
@@ -0,0 +1,7 @@
+.class LManyRegisters;
+.super Ljava/lang/Object;
+
+.method public manyRegisters()V
+ .registers 65535
+ return-void
+.end method \ No newline at end of file
diff --git a/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dex b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dex
new file mode 100644
index 00000000..6ef3d349
--- /dev/null
+++ b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dex
Binary files differ
diff --git a/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali
new file mode 100644
index 00000000..b4fd27d8
--- /dev/null
+++ b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali
@@ -0,0 +1,72 @@
+.class public LMultiSwitch;
+.super Ljava/lang/Object;
+.source "Format31t.smali"
+
+.method public multi-packed-switch()V
+ .registers 1
+ const p0, 0xc
+ packed-switch p0, :pswitch_data_12
+ goto :goto_b
+ :pswitch_7
+ return-void
+ :pswitch_8
+ return-void
+ :pswitch_9
+ return-void
+ :pswitch_a
+ return-void
+ :goto_b
+ packed-switch p0, :pswitch_data_12
+ nop
+ return-void
+ :pswitch_f
+ return-void
+ :pswitch_10
+ return-void
+ :pswitch_11
+ return-void
+ :pswitch_12
+ :pswitch_data_12
+ .packed-switch 0xa
+ :pswitch_7
+ :pswitch_8
+ :pswitch_9
+ :pswitch_a
+ .end packed-switch
+
+.end method
+
+.method public multi-sparse-switch()V
+ .registers 1
+ const p0, 0xd
+ sparse-switch p0, :sswitch_data_12
+ goto :goto_b
+ :sswitch_7
+ return-void
+ :sswitch_8
+ return-void
+ :sswitch_9
+ return-void
+ :sswitch_a
+ return-void
+ :goto_b
+ sparse-switch p0, :sswitch_data_12
+ nop
+ return-void
+ :sswitch_f
+ return-void
+ :sswitch_10
+ return-void
+ :sswitch_11
+ return-void
+
+ :sswitch_12
+
+ :sswitch_data_12
+ .sparse-switch
+ 0xa -> :sswitch_7
+ 0xf -> :sswitch_9
+ 0x14 -> :sswitch_8
+ 0x63 -> :sswitch_a
+ .end sparse-switch
+.end method \ No newline at end of file
diff --git a/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali
new file mode 100644
index 00000000..f3aeeed5
--- /dev/null
+++ b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali
@@ -0,0 +1,119 @@
+.class public LMultiSwitch;
+.super Ljava/lang/Object;
+.source "Format31t.smali"
+
+
+# virtual methods
+.method public multi-packed-switch()V
+ .registers 1
+
+ const p0, 0xc
+
+ packed-switch p0, :pswitch_data_14
+
+ goto :goto_b
+
+ :pswitch_7
+ return-void
+
+ :pswitch_8
+ return-void
+
+ :pswitch_9
+ return-void
+
+ :pswitch_a
+ return-void
+
+ :goto_b
+ packed-switch p0, :pswitch_data_20
+
+ nop
+
+ :pswitch_f
+ return-void
+
+ :pswitch_10
+ return-void
+
+ :pswitch_11
+ return-void
+
+ :pswitch_12
+ return-void
+
+ nop
+
+ :pswitch_data_14
+ .packed-switch 0xa
+ :pswitch_7
+ :pswitch_8
+ :pswitch_9
+ :pswitch_a
+ .end packed-switch
+
+ :pswitch_data_20
+ .packed-switch 0xa
+ :pswitch_f
+ :pswitch_10
+ :pswitch_11
+ :pswitch_12
+ .end packed-switch
+.end method
+
+.method public multi-sparse-switch()V
+ .registers 1
+
+ const p0, 0xd
+
+ sparse-switch p0, :sswitch_data_14
+
+ goto :goto_b
+
+ :sswitch_7
+ return-void
+
+ :sswitch_8
+ return-void
+
+ :sswitch_9
+ return-void
+
+ :sswitch_a
+ return-void
+
+ :goto_b
+ sparse-switch p0, :sswitch_data_26
+
+ nop
+
+ :sswitch_f
+ return-void
+
+ :sswitch_10
+ return-void
+
+ :sswitch_11
+ return-void
+
+ :sswitch_12
+ return-void
+
+ nop
+
+ :sswitch_data_14
+ .sparse-switch
+ 0xa -> :sswitch_7
+ 0xf -> :sswitch_9
+ 0x14 -> :sswitch_8
+ 0x63 -> :sswitch_a
+ .end sparse-switch
+
+ :sswitch_data_26
+ .sparse-switch
+ 0xa -> :sswitch_f
+ 0xf -> :sswitch_11
+ 0x14 -> :sswitch_10
+ 0x63 -> :sswitch_12
+ .end sparse-switch
+.end method
diff --git a/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali
new file mode 100644
index 00000000..6e3d23d4
--- /dev/null
+++ b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali
@@ -0,0 +1,35 @@
+.class public LUnorderedSparseSwitch;
+.super Ljava/lang/Object;
+
+.method public static test_sparse-switch()V
+ .registers 1
+
+ const v0, 13
+
+ sparse-switch v0, :SparseSwitch
+
+:Label10
+ return-void
+
+:Label20
+ return-void
+
+:Label15
+ return-void
+
+:Label13
+ return-void
+
+:Label99
+ return-void
+
+# Note: unordered keys
+:SparseSwitch
+ .sparse-switch
+ 10 -> :Label10
+ 20 -> :Label20
+ 15 -> :Label15
+ 99 -> :Label99
+ 13 -> :Label13
+ .end sparse-switch
+.end method \ No newline at end of file
diff --git a/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali
new file mode 100644
index 00000000..c4c455b6
--- /dev/null
+++ b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali
@@ -0,0 +1,28 @@
+.class public LUnorderedSparseSwitch;
+.super Ljava/lang/Object;
+.method public static test_sparse-switch()V
+.registers 1
+const v0, 0xd
+sparse-switch v0, :sswitch_data_c
+:sswitch_6
+return-void
+:sswitch_7
+return-void
+:sswitch_8
+return-void
+:sswitch_9
+return-void
+:sswitch_a
+return-void
+nop
+
+# Note: ordered keys
+:sswitch_data_c
+.sparse-switch
+0xa -> :sswitch_6
+0xd -> :sswitch_9
+0xf -> :sswitch_8
+0x14 -> :sswitch_7
+0x63 -> :sswitch_a
+.end sparse-switch
+.end method \ No newline at end of file
diff --git a/build.gradle b/build.gradle
index a9ee7e0e..f0d1e00a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -39,7 +39,7 @@ if (!('release' in gradle.startParameter.taskNames)) {
def versionSuffix
try {
def git = org.eclipse.jgit.api.Git.open(file('.'))
- def head = git.getRepository().getRef("HEAD")
+ def head = git.getRepository().getRef('HEAD')
versionSuffix = head.getObjectId().abbreviate(8).name()
if (!git.status().call().clean) {
@@ -90,7 +90,9 @@ subprojects {
stringtemplate: 'org.antlr:stringtemplate:3.2.1',
commons_cli: 'commons-cli:commons-cli:1.2',
jflex: 'de.jflex:jflex:1.4.3',
- proguard: 'net.sf.proguard:proguard-base:4.8'
+ jflex_plugin: 'co.tomlee.gradle.plugins:gradle-jflex-plugin:0.0.1',
+ proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1',
+ dx: 'com.google.android.tools:dx:1.7'
]
}
@@ -124,7 +126,7 @@ subprojects {
}
signing {
- required { gradle.taskGraph.hasTask("uploadArchives") }
+ required { gradle.taskGraph.hasTask('uploadArchives') }
sign configurations.archives
}
@@ -135,7 +137,7 @@ subprojects {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
if (rootProject.hasProperty('sonatypeUsername') && rootProject.hasProperty('sonatypePassword')) {
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
+ repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') {
authentication(userName: sonatypeUsername, password: sonatypePassword)
}
}
@@ -182,5 +184,5 @@ buildscript {
}
task wrapper(type: Wrapper) {
- gradleVersion = '1.10'
+ gradleVersion = '2.3'
} \ No newline at end of file
diff --git a/deodexerant/Android.mk b/deodexerant/Android.mk
index c1921f9b..46a9dba8 100644
--- a/deodexerant/Android.mk
+++ b/deodexerant/Android.mk
@@ -39,4 +39,6 @@ LOCAL_SHARED_LIBRARIES := libdl
LOCAL_MODULE_TAGS := optional
-include $(BUILD_EXECUTABLE) \ No newline at end of file
+LOCAL_LDFLAGS := -Wl,--hash-style=sysv
+
+include $(BUILD_EXECUTABLE)
diff --git a/dexlib2/build.gradle b/dexlib2/build.gradle
index d39c5db9..dc3e853a 100644
--- a/dexlib2/build.gradle
+++ b/dexlib2/build.gradle
@@ -31,6 +31,7 @@
configurations {
accessorTestGenerator
+ dx
}
dependencies {
@@ -41,42 +42,13 @@ dependencies {
testCompile depends.junit
accessorTestGenerator project('accessorTestGenerator')
+
+ dx depends.dx
}
ext.testAccessorOutputDir = file("${buildDir}/generated-accessor-test-sources")
ext.testAccessorOutputFile = file("${buildDir}/generated-accessor-test-sources/org/jf/dexlib2/AccessorTypes.java")
-sourceSets {
- // The sources for building the test dex file for the accessor test
- accessorTestDex {
- java {
- srcDir testAccessorOutputDir
- }
- }
-
- // The sources for the accessor test itself
- accessorTest {
- java {
- compileClasspath += main.output
- runtimeClasspath += main.output
- }
- }
-}
-
-configurations {
- accessorTestDexCompile.extendsFrom compile
- accessorTestDexRuntime.extendsFrom runtime
-
- accessorTestCompile.extendsFrom testCompile
- accessorTestRuntime.extendsFrom testRuntime
-}
-
-idea {
- module {
- testSourceDirs += sourceSets.accessorTest.java.srcDirs
- }
-}
-
// You must manually execute this task to regenerate SyntheticAccessorFSM.java, after modifying the ragel file
// e.g. ./gradlew ragel
task ragel(type:Exec) {
@@ -87,36 +59,48 @@ task ragel(type:Exec) {
}
task generateAccessorTestSource(type: JavaExec) {
- outputs.dir file(testAccessorOutputDir)
+ doFirst {
+ file(testAccessorOutputFile.parent).mkdirs()
+ }
- mkdir(file(testAccessorOutputFile).parent)
+ outputs.dir file(testAccessorOutputDir)
+ sourceSets['test'].java.srcDir file(testAccessorOutputDir)
classpath = configurations.accessorTestGenerator
main = 'org.jf.dexlib2.AccessorTestGenerator'
args testAccessorOutputFile
}
-compileAccessorTestDexJava.dependsOn(generateAccessorTestSource)
+compileTestJava.dependsOn generateAccessorTestSource
-task generateAccessorTestDex(type: Exec, dependsOn: compileAccessorTestDexJava) {
- def outputDex = file("${sourceSets.accessorTest.output.resourcesDir}/accessorTest.dex")
- mkdir(outputDex.parent)
+task generateAccessorTestDex(type: JavaExec, dependsOn: compileTestJava) {
+ def outputDex = file(new File(sourceSets.test.output.resourcesDir, 'accessorTest.dex'))
- inputs.dir project.sourceSets.accessorTestDex.output.classesDir
+ doFirst {
+ file(outputDex.parent).mkdirs()
+
+ // this has to be done in doFirst, so that the generated classes will be available.
+ // otherwise, it the tree will be populated while the build is being configured,
+ // which is before the compileTestJava has run
+ fileTree(project.sourceSets.test.output.classesDir) {
+ include 'org/jf/dexlib2/AccessorTypes*.class'
+ }.each { File file ->
+ args file
+ }
+ }
+
+ inputs.dir(project.sourceSets.test.output.classesDir)
outputs.file outputDex
- sourceSets.accessorTest.resources
+ main 'com.android.dx.command.Main'
+ classpath = configurations.dx
- workingDir project.sourceSets.accessorTestDex.output.classesDir
- executable 'dx'
+ workingDir project.sourceSets.test.output.classesDir
+ //executable 'dx'
args '--dex'
+ args '--no-strict'
args "--output=${outputDex}"
- args '.'
-}
-
-task accessorTest(type: Test, dependsOn: generateAccessorTestDex) {
- testClassesDir = project.sourceSets.accessorTest.output.classesDir
- classpath = project.sourceSets.accessorTest.runtimeClasspath
}
+test.dependsOn generateAccessorTestDex
uploadArchives {
repositories.mavenDeployer {
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
index d08da036..a1ddee2e 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java
@@ -45,22 +45,26 @@ import java.util.zip.ZipFile;
public final class DexFileFactory {
@Nonnull
- public static DexBackedDexFile loadDexFile(String path, int api) throws IOException {
- return loadDexFile(new File(path), "classes.dex", new Opcodes(api));
+ public static DexBackedDexFile loadDexFile(String path, int api, boolean experimental)
+ throws IOException {
+ return loadDexFile(new File(path), "classes.dex", new Opcodes(api, experimental));
}
@Nonnull
- public static DexBackedDexFile loadDexFile(File dexFile, int api) throws IOException {
- return loadDexFile(dexFile, "classes.dex", new Opcodes(api));
+ public static DexBackedDexFile loadDexFile(File dexFile, int api, boolean experimental)
+ throws IOException {
+ return loadDexFile(dexFile, "classes.dex", new Opcodes(api, experimental));
}
@Nonnull
- public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry, int api) throws IOException {
- return loadDexFile(dexFile, dexEntry, new Opcodes(api));
+ public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry, int api,
+ boolean experimental) throws IOException {
+ return loadDexFile(dexFile, dexEntry, new Opcodes(api, experimental));
}
@Nonnull
- public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry, @Nonnull Opcodes opcodes) throws IOException {
+ public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry,
+ @Nonnull Opcodes opcodes) throws IOException {
ZipFile zipFile = null;
boolean isZipFile = false;
try {
@@ -98,19 +102,22 @@ public final class DexFileFactory {
}
InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile));
-
try {
- return DexBackedDexFile.fromInputStream(opcodes, inputStream);
- } catch (DexBackedDexFile.NotADexFile ex) {
- // just eat it
- }
+ try {
+ return DexBackedDexFile.fromInputStream(opcodes, inputStream);
+ } catch (DexBackedDexFile.NotADexFile ex) {
+ // just eat it
+ }
- // Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails
+ // 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
+ try {
+ return DexBackedOdexFile.fromInputStream(opcodes, inputStream);
+ } catch (DexBackedOdexFile.NotAnOdexFile ex) {
+ // just eat it
+ }
+ } finally {
+ inputStream.close();
}
throw new ExceptionWithContext("%s is not an apk, dex file or odex file.", dexFile.getPath());
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Format.java b/dexlib2/src/main/java/org/jf/dexlib2/Format.java
index d91b7432..ee34aa50 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/Format.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/Format.java
@@ -51,6 +51,7 @@ public enum Format {
Format22t(4),
Format22x(4),
Format23x(4),
+ Format25x(4),
Format30t(6),
Format31c(6),
Format31i(6),
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java b/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java
index d0adee8c..3b082ee8 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java
@@ -269,13 +269,13 @@ public enum Opcode
INVOKE_OBJECT_INIT_RANGE((short)0xf0, "invoke-object-init/range", minApi(14), ReferenceType.METHOD, Format.Format3rc, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT | Opcode.CAN_INITIALIZE_REFERENCE),
RETURN_VOID_BARRIER((short)0xf1, "return-void-barrier", minApi(11), ReferenceType.NONE, Format.Format10x, Opcode.ODEX_ONLY),
IGET_QUICK((short)0xf2, "iget-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
- IGET_WIDE_QUICK((short)0xf3, "iget-wide-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
- IGET_OBJECT_QUICK((short)0xf4, "iget-object-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
- IPUT_QUICK((short)0xf5, "iput-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
- IPUT_WIDE_QUICK((short)0xf6, "iput-wide-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
- IPUT_OBJECT_QUICK((short)0xf7, "iput-object-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
- INVOKE_VIRTUAL_QUICK((short)0xf8, "invoke-virtual-quick", ReferenceType.NONE, Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
- INVOKE_VIRTUAL_QUICK_RANGE((short)0xf9, "invoke-virtual-quick/range", ReferenceType.NONE, Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+ IGET_WIDE_QUICK((short)0xf3, "iget-wide-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
+ IGET_OBJECT_QUICK((short)0xf4, "iget-object-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
+ IPUT_QUICK((short)0xf5, "iput-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+ IPUT_WIDE_QUICK((short)0xf6, "iput-wide-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+ IPUT_OBJECT_QUICK((short)0xf7, "iput-object-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
+ INVOKE_VIRTUAL_QUICK((short)0xf8, "invoke-virtual-quick", maxApi(22), ReferenceType.NONE, Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
+ INVOKE_VIRTUAL_QUICK_RANGE((short)0xf9, "invoke-virtual-quick/range", maxApi(22), ReferenceType.NONE, Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
INVOKE_SUPER_QUICK((short)0xfa, "invoke-super-quick", ReferenceType.NONE, Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
INVOKE_SUPER_QUICK_RANGE((short)0xfb, "invoke-super-quick/range", ReferenceType.NONE, Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
@@ -285,7 +285,17 @@ public enum Opcode
PACKED_SWITCH_PAYLOAD((short)0x100, "packed-switch-payload", ReferenceType.NONE, Format.PackedSwitchPayload, 0),
SPARSE_SWITCH_PAYLOAD((short)0x200, "sparse-switch-payload", ReferenceType.NONE, Format.SparseSwitchPayload, 0),
- ARRAY_PAYLOAD((short)0x300, "array-payload", ReferenceType.NONE, Format.ArrayPayload, 0);
+ ARRAY_PAYLOAD((short)0x300, "array-payload", ReferenceType.NONE, Format.ArrayPayload, 0),
+
+ // Reuse the deprecated f3-ff opcodes in Art:
+ INVOKE_LAMBDA((short)0xf3, "invoke-lambda", minApi(23), ReferenceType.NONE, Format.Format25x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT | Opcode.EXPERIMENTAL),
+ // TODO: What about JUMBO support if the string ID is too large?
+ CAPTURE_VARIABLE((short)0xf5, "capture-variable", minApi(23), ReferenceType.STRING, Format.Format21c, Opcode.EXPERIMENTAL),
+ CREATE_LAMBDA((short)0xf6, "create-lambda", minApi(23), ReferenceType.METHOD, Format.Format21c, Opcode.SETS_REGISTER | Opcode.EXPERIMENTAL),
+ // TODO: do we need a capture/liberate wide?
+ LIBERATE_VARIABLE((short)0xf7, "liberate-variable", minApi(23), ReferenceType.STRING, Format.Format22c, Opcode.SETS_REGISTER | Opcode.EXPERIMENTAL),
+ BOX_LAMBDA((short)0xf8, "box-lambda", minApi(23), ReferenceType.NONE, Format.Format22x, Opcode.SETS_REGISTER | Opcode.EXPERIMENTAL),
+ UNBOX_LAMBDA((short)0xf9, "unbox-lambda", minApi(23), ReferenceType.TYPE, Format.Format22c, Opcode.SETS_REGISTER | Opcode.EXPERIMENTAL);
//if the instruction can throw an exception
public static final int CAN_THROW = 0x1;
@@ -309,6 +319,8 @@ public enum Opcode
public static final int JUMBO_OPCODE = 0x200;
//if the instruction can initialize an uninitialized object reference
public static final int CAN_INITIALIZE_REFERENCE = 0x400;
+ //if the instruction is experimental (not potentially supported by Android runtime yet)
+ public static final int EXPERIMENTAL = 0x800;
private static final int ALL_APIS = 0xFFFF0000;
@@ -417,4 +429,8 @@ public enum Opcode
public final boolean canInitializeReference() {
return (flags & CAN_INITIALIZE_REFERENCE) != 0;
}
+
+ public final boolean isExperimental() {
+ return (flags & EXPERIMENTAL) != 0;
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java b/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
index d6e5532e..dd876813 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java
@@ -40,13 +40,14 @@ public class Opcodes {
private final Opcode[] opcodesByValue;
private final HashMap<String, Opcode> opcodesByName;
- public Opcodes(int api) {
+ public Opcodes(int api, boolean experimental) {
opcodesByValue = new Opcode[256];
opcodesByName = Maps.newHashMap();
for (Opcode opcode: Opcode.values()) {
if (!opcode.format.isPayloadFormat) {
- if (api <= opcode.getMaxApi() && api >= opcode.getMinApi()) {
+ if (api <= opcode.getMaxApi() && api >= opcode.getMinApi() &&
+ (experimental || !opcode.isExperimental())) {
opcodesByValue[opcode.value] = opcode;
opcodesByName.put(opcode.name.toLowerCase(), opcode);
}
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 7efeb408..bd9cfb1b 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java
@@ -164,18 +164,18 @@ public class ClassPath {
@Nonnull
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
- int api) {
- return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17);
+ 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) {
+ int api, boolean checkPackagePrivateAccess, boolean experimental) {
ArrayList<DexFile> dexFiles = Lists.newArrayList();
for (String classPathEntry: classPath) {
try {
- dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api));
+ dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api, experimental));
} catch (ExceptionWithContext e){}
}
dexFiles.add(dexFile);
@@ -186,7 +186,8 @@ public class ClassPath {
@Nonnull
private static DexFile loadClassPathEntry(@Nonnull Iterable<String> classPathDirs,
- @Nonnull String bootClassPathEntry, int api) {
+ @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();
@@ -221,7 +222,7 @@ public class ClassPath {
"warning: cannot open %s for reading. Will continue looking.", file.getPath()));
} else {
try {
- return DexFileFactory.loadDexFile(file, api);
+ return DexFileFactory.loadDexFile(file, api, experimental);
} catch (DexFileFactory.NoClassesDexException ex) {
// ignore and continue
} catch (Exception ex) {
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java
index d57ce961..2bb3e492 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java
@@ -73,6 +73,7 @@ public class DumpFields {
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];
@@ -88,6 +89,9 @@ public class DumpFields {
case 'a':
apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
+ case 'X':
+ experimental = true;
+ break;
default:
assert false;
}
@@ -107,9 +111,9 @@ public class DumpFields {
}
try {
- DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel);
+ 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);
+ ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental);
FileOutputStream outStream = new FileOutputStream(outFile);
for (ClassDef classDef: dexFile.getClasses()) {
@@ -163,8 +167,14 @@ public class DumpFields {
.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
index 0d7656c3..193c0d39 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java
@@ -71,6 +71,7 @@ public class DumpVtables {
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];
@@ -86,6 +87,9 @@ public class DumpVtables {
case 'a':
apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
+ case 'X':
+ experimental = true;
+ break;
default:
assert false;
}
@@ -105,9 +109,9 @@ public class DumpVtables {
}
try {
- DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel);
+ 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);
+ ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental);
FileOutputStream outStream = new FileOutputStream(outFile);
for (ClassDef classDef: dexFile.getClasses()) {
@@ -167,8 +171,14 @@ public class DumpVtables {
.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/builder/MutableMethodImplementation.java b/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java
index f734e018..84981221 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java
@@ -511,82 +511,90 @@ public class MutableMethodImplementation implements MethodImplementation {
@Nonnull Instruction instruction) {
switch (instruction.getOpcode().format) {
case Format10t:
- setInstruction(location, newBuilderInstruction10t(location.codeAddress, codeAddressToIndex,
- (Instruction10t)instruction));
+ setInstruction(location, newBuilderInstruction10t(location.codeAddress,
+ codeAddressToIndex,
+ (Instruction10t) instruction));
return;
case Format10x:
- setInstruction(location, newBuilderInstruction10x((Instruction10x)instruction));
+ setInstruction(location, newBuilderInstruction10x((Instruction10x) instruction));
return;
case Format11n:
- setInstruction(location, newBuilderInstruction11n((Instruction11n)instruction));
+ setInstruction(location, newBuilderInstruction11n((Instruction11n) instruction));
return;
case Format11x:
- setInstruction(location, newBuilderInstruction11x((Instruction11x)instruction));
+ setInstruction(location, newBuilderInstruction11x((Instruction11x) instruction));
return;
case Format12x:
- setInstruction(location, newBuilderInstruction12x((Instruction12x)instruction));
+ setInstruction(location, newBuilderInstruction12x((Instruction12x) instruction));
return;
case Format20bc:
- setInstruction(location, newBuilderInstruction20bc((Instruction20bc)instruction));
+ setInstruction(location, newBuilderInstruction20bc((Instruction20bc) instruction));
return;
case Format20t:
- setInstruction(location, newBuilderInstruction20t(location.codeAddress, codeAddressToIndex,
- (Instruction20t)instruction));
+ setInstruction(location, newBuilderInstruction20t(location.codeAddress,
+ codeAddressToIndex,
+ (Instruction20t) instruction));
return;
case Format21c:
- setInstruction(location, newBuilderInstruction21c((Instruction21c)instruction));
+ setInstruction(location, newBuilderInstruction21c((Instruction21c) instruction));
return;
case Format21ih:
- setInstruction(location, newBuilderInstruction21ih((Instruction21ih)instruction));
+ setInstruction(location, newBuilderInstruction21ih((Instruction21ih) instruction));
return;
case Format21lh:
- setInstruction(location, newBuilderInstruction21lh((Instruction21lh)instruction));
+ setInstruction(location, newBuilderInstruction21lh((Instruction21lh) instruction));
return;
case Format21s:
- setInstruction(location, newBuilderInstruction21s((Instruction21s)instruction));
+ setInstruction(location, newBuilderInstruction21s((Instruction21s) instruction));
return;
case Format21t:
- setInstruction(location, newBuilderInstruction21t(location.codeAddress, codeAddressToIndex,
- (Instruction21t)instruction));
+ setInstruction(location, newBuilderInstruction21t(location.codeAddress,
+ codeAddressToIndex,
+ (Instruction21t) instruction));
return;
case Format22b:
- setInstruction(location, newBuilderInstruction22b((Instruction22b)instruction));
+ setInstruction(location, newBuilderInstruction22b((Instruction22b) instruction));
return;
case Format22c:
- setInstruction(location, newBuilderInstruction22c((Instruction22c)instruction));
+ setInstruction(location, newBuilderInstruction22c((Instruction22c) instruction));
return;
case Format22s:
- setInstruction(location, newBuilderInstruction22s((Instruction22s)instruction));
+ setInstruction(location, newBuilderInstruction22s((Instruction22s) instruction));
return;
case Format22t:
- setInstruction(location, newBuilderInstruction22t(location.codeAddress, codeAddressToIndex,
- (Instruction22t)instruction));
+ setInstruction(location, newBuilderInstruction22t(location.codeAddress,
+ codeAddressToIndex,
+ (Instruction22t) instruction));
return;
case Format22x:
- setInstruction(location, newBuilderInstruction22x((Instruction22x)instruction));
+ setInstruction(location, newBuilderInstruction22x((Instruction22x) instruction));
return;
case Format23x:
- setInstruction(location, newBuilderInstruction23x((Instruction23x)instruction));
+ setInstruction(location, newBuilderInstruction23x((Instruction23x) instruction));
+ return;
+ case Format25x:
+ setInstruction(location, newBuilderInstruction25x((Instruction25x) instruction));
return;
case Format30t:
- setInstruction(location, newBuilderInstruction30t(location.codeAddress, codeAddressToIndex,
- (Instruction30t)instruction));
+ setInstruction(location, newBuilderInstruction30t(location.codeAddress,
+ codeAddressToIndex,
+ (Instruction30t) instruction));
return;
case Format31c:
- setInstruction(location, newBuilderInstruction31c((Instruction31c)instruction));
+ setInstruction(location, newBuilderInstruction31c((Instruction31c) instruction));
return;
case Format31i:
- setInstruction(location, newBuilderInstruction31i((Instruction31i)instruction));
+ setInstruction(location, newBuilderInstruction31i((Instruction31i) instruction));
return;
case Format31t:
setInstruction(location, newBuilderInstruction31t(location, codeAddressToIndex,
- (Instruction31t)instruction));
+ (Instruction31t) instruction));
return;
case Format32x:
- setInstruction(location, newBuilderInstruction32x((Instruction32x)instruction));
+ setInstruction(location, newBuilderInstruction32x((Instruction32x) instruction));
return;
case Format35c:
- setInstruction(location, newBuilderInstruction35c((Instruction35c)instruction));
+ setInstruction(location, newBuilderInstruction35c((Instruction35c) instruction));
return;
case Format3rc:
setInstruction(location, newBuilderInstruction3rc((Instruction3rc)instruction));
@@ -821,6 +829,18 @@ public class MutableMethodImplementation implements MethodImplementation {
}
@Nonnull
+ private BuilderInstruction25x newBuilderInstruction25x(@Nonnull Instruction25x instruction) {
+ return new BuilderInstruction25x(
+ instruction.getOpcode(),
+ instruction.getParameterRegisterCount(),
+ instruction.getRegisterFixedC(),
+ instruction.getRegisterParameterD(),
+ instruction.getRegisterParameterE(),
+ instruction.getRegisterParameterF(),
+ instruction.getRegisterParameterG());
+ }
+
+ @Nonnull
private BuilderInstruction3rc newBuilderInstruction3rc(@Nonnull Instruction3rc instruction) {
return new BuilderInstruction3rc(
instruction.getOpcode(),
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/builder/instruction/BuilderInstruction25x.java b/dexlib2/src/main/java/org/jf/dexlib2/builder/instruction/BuilderInstruction25x.java
new file mode 100644
index 00000000..3783d2b6
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/builder/instruction/BuilderInstruction25x.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2015, 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.builder.instruction;
+
+import org.jf.dexlib2.Format;
+import org.jf.dexlib2.Opcode;
+import org.jf.dexlib2.builder.BuilderInstruction;
+import org.jf.dexlib2.iface.instruction.formats.Instruction25x;
+import org.jf.dexlib2.util.Preconditions;
+
+import javax.annotation.Nonnull;
+
+public class BuilderInstruction25x extends BuilderInstruction implements Instruction25x {
+ public static final Format FORMAT = Format.Format25x;
+
+ protected final int parameterRegisterCount;
+ protected final int registerClosure;
+ protected final int registerD;
+ protected final int registerE;
+ protected final int registerF;
+ protected final int registerG;
+
+ public BuilderInstruction25x(@Nonnull Opcode opcode,
+ int parameterRegisterCount,
+ int registerClosure,
+ int registerD,
+ int registerE,
+ int registerF,
+ int registerG) {
+ super(opcode);
+ this.parameterRegisterCount =
+ Preconditions.check25xParameterRegisterCount(parameterRegisterCount);
+ this.registerClosure = Preconditions.checkNibbleRegister(registerClosure); //at least 1 reg
+ this.registerD = (parameterRegisterCount>0) ?
+ Preconditions.checkNibbleRegister(registerD) : 0;
+ this.registerE = (parameterRegisterCount>1) ?
+ Preconditions.checkNibbleRegister(registerE) : 0;
+ this.registerF = (parameterRegisterCount>2) ?
+ Preconditions.checkNibbleRegister(registerF) : 0;
+ this.registerG = (parameterRegisterCount>3) ?
+ Preconditions.checkNibbleRegister(registerG) : 0;
+ }
+
+ @Override public int getRegisterCount() { return parameterRegisterCount + 1; }
+ @Override public int getParameterRegisterCount() { return parameterRegisterCount; }
+ @Override public int getRegisterFixedC() { return registerClosure; }
+ @Override public int getRegisterParameterD() { return registerD; }
+ @Override public int getRegisterParameterE() { return registerE; }
+ @Override public int getRegisterParameterF() { return registerF; }
+ @Override public int getRegisterParameterG() { return registerG; }
+
+ @Override public Format getFormat() { return FORMAT; }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java
index a4c29990..ac82f4b4 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java
@@ -115,6 +115,8 @@ public abstract class DexBackedInstruction implements Instruction {
return new DexBackedInstruction22x(dexFile, opcode, instructionStartOffset);
case Format23x:
return new DexBackedInstruction23x(dexFile, opcode, instructionStartOffset);
+ case Format25x:
+ return new DexBackedInstruction25x(dexFile, opcode, instructionStartOffset);
case Format30t:
return new DexBackedInstruction30t(dexFile, opcode, instructionStartOffset);
case Format31c:
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction25x.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction25x.java
new file mode 100644
index 00000000..80fb8767
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction25x.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015, 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.instruction;
+
+import org.jf.dexlib2.Opcode;
+import org.jf.dexlib2.dexbacked.DexBackedDexFile;
+import org.jf.dexlib2.iface.instruction.formats.Instruction25x;
+import org.jf.util.NibbleUtils;
+
+import javax.annotation.Nonnull;
+
+public class DexBackedInstruction25x extends DexBackedInstruction implements Instruction25x {
+ public DexBackedInstruction25x(@Nonnull DexBackedDexFile dexFile,
+ @Nonnull Opcode opcode,
+ int instructionStart) {
+ super(dexFile, opcode, instructionStart);
+ }
+
+ @Override
+ public int getRegisterCount() {
+ return getParameterRegisterCount() + 1;
+ }
+
+ @Override
+ public int getParameterRegisterCount() {
+ return NibbleUtils.extractHighUnsignedNibble(dexFile.readUbyte(instructionStart + 1));
+ }
+
+ @Override
+ public int getRegisterFixedC() {
+ return NibbleUtils.extractLowUnsignedNibble(dexFile.readUbyte(instructionStart + 2));
+ }
+
+ @Override
+ public int getRegisterParameterD() {
+ return NibbleUtils.extractHighUnsignedNibble(dexFile.readUbyte(instructionStart + 2));
+ }
+
+ @Override
+ public int getRegisterParameterE() {
+ return NibbleUtils.extractLowUnsignedNibble(dexFile.readUbyte(instructionStart + 3));
+ }
+
+ @Override
+ public int getRegisterParameterF() {
+ return NibbleUtils.extractHighUnsignedNibble(dexFile.readUbyte(instructionStart + 3));
+ }
+
+ @Override
+ public int getRegisterParameterG() {
+ return NibbleUtils.extractLowUnsignedNibble(dexFile.readUbyte(instructionStart + 1));
+ }
+
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java
index c3946976..9c79e270 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java
@@ -129,6 +129,9 @@ public class CodeItem {
case Format10x:
annotateInstruction10x(out, instruction);
break;
+ case Format25x:
+ annotateInstruction25x(out, (Instruction25x) instruction);
+ break;
case Format35c:
annotateInstruction35c(out, (Instruction35c)instruction);
break;
@@ -282,6 +285,30 @@ public class CodeItem {
instruction.getOpcode().name, Joiner.on(", ").join(args), reference));
}
+ private void annotateInstruction25x(@Nonnull AnnotatedBytes out,
+ @Nonnull Instruction25x instruction) {
+ List<String> args = Lists.newArrayList();
+
+ int registerCount = instruction.getRegisterCount(); //at least 1.
+ if (registerCount == 2) {
+ args.add(formatRegister(instruction.getRegisterParameterD()));
+ } else if (registerCount == 3) {
+ args.add(formatRegister(instruction.getRegisterParameterD()));
+ args.add(formatRegister(instruction.getRegisterParameterE()));
+ } else if (registerCount == 4) {
+ args.add(formatRegister(instruction.getRegisterParameterD()));
+ args.add(formatRegister(instruction.getRegisterParameterE()));
+ args.add(formatRegister(instruction.getRegisterParameterF()));
+ } else if (registerCount == 5) {
+ args.add(formatRegister(instruction.getRegisterParameterD()));
+ args.add(formatRegister(instruction.getRegisterParameterE()));
+ args.add(formatRegister(instruction.getRegisterParameterF()));
+ args.add(formatRegister(instruction.getRegisterParameterG()));
+ }
+ out.annotate(6, String.format("%s %s, {%s}",
+ instruction.getOpcode().name, instruction.getRegisterFixedC(), Joiner.on(", ").join(args)));
+ }
+
private void annotateInstruction3rc(@Nonnull AnnotatedBytes out, @Nonnull Instruction3rc instruction) {
int startRegister = instruction.getStartRegister();
int endRegister = startRegister + instruction.getRegisterCount() - 1;
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotatable.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotatable.java
new file mode 100644
index 00000000..f16c207b
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotatable.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015, 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 java.util.Set;
+
+/**
+ * This class represents an object that can have Annotations applied to it
+ */
+public interface Annotatable {
+ /**
+ * Gets a set of the annotations that are applied to this object.
+ *
+ * The annotations in the returned set are guaranteed to have unique types.
+ *
+ * @return A set of the annotations that are applied to this object
+ */
+ @Nonnull Set<? extends Annotation> getAnnotations();
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotation.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotation.java
index 251ac123..7c107a45 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotation.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotation.java
@@ -55,7 +55,7 @@ public interface Annotation extends BasicAnnotation, Comparable<Annotation> {
*
* @return The type of this annotation
*/
- @Nonnull String getType();
+ @Nonnull @Override String getType();
/**
* Gets a set of the name/value elements associated with this annotation.
@@ -64,7 +64,7 @@ public interface Annotation extends BasicAnnotation, Comparable<Annotation> {
*
* @return A set of AnnotationElements
*/
- @Nonnull Set<? extends AnnotationElement> getElements();
+ @Nonnull @Override Set<? extends AnnotationElement> getElements();
/**
* Returns a hashcode for this Annotation.
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/ClassDef.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/ClassDef.java
index 73ab8690..31d7fb99 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/iface/ClassDef.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/ClassDef.java
@@ -43,7 +43,7 @@ import java.util.Set;
* It also acts as a TypeReference to itself. Any equality/comparison is based on its identity as a TypeReference,
* and shouldn't take into account anything other than the type of this class.
*/
-public interface ClassDef extends TypeReference {
+public interface ClassDef extends TypeReference, Annotatable {
/**
* Gets the class type.
*
@@ -95,7 +95,7 @@ public interface ClassDef extends TypeReference {
*
* @return A set of the annotations that are applied to this class
*/
- @Nonnull Set<? extends Annotation> getAnnotations();
+ @Override @Nonnull Set<? extends Annotation> getAnnotations();
/**
* Gets the static fields that are defined by this class.
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Field.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Field.java
index 7b4aad38..0091929b 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/iface/Field.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Field.java
@@ -44,27 +44,27 @@ import java.util.Set;
* It also acts as a FieldReference to itself. Any equality/comparison is based on its identity as a FieldReference,
* and shouldn't take into account any non-FieldReference specifics of this field.
*/
-public interface Field extends FieldReference {
+public interface Field extends FieldReference, Member {
/**
* Gets the type of the class that defines this field.
*
* @return The type of the class that defines this field
*/
- @Nonnull String getDefiningClass();
+ @Override @Nonnull String getDefiningClass();
/**
* Gets the name of this field.
*
* @return The name of this field
*/
- @Nonnull String getName();
+ @Override @Nonnull String getName();
/**
* Gets the type of this field.
*
* @return The type of this field
*/
- @Nonnull String getType();
+ @Override @Nonnull String getType();
/**
* Gets the access flags for this field.
@@ -73,7 +73,7 @@ public interface Field extends FieldReference {
*
* @return The access flags for this field
*/
- int getAccessFlags();
+ @Override int getAccessFlags();
/**
* Gets the initial value for this field, if available.
@@ -92,5 +92,5 @@ public interface Field extends FieldReference {
*
* @return A set of the annotations that are applied to this field
*/
- @Nonnull Set<? extends Annotation> getAnnotations();
+ @Override @Nonnull Set<? extends Annotation> getAnnotations();
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Member.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Member.java
new file mode 100644
index 00000000..343c4032
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Member.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015, 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;
+
+/**
+ * This class represents a generic class member
+ */
+public interface Member extends Annotatable {
+ /**
+ * Gets the type of the class that defines this member.
+ *
+ * @return The type of the class that defines this member
+ */
+ @Nonnull String getDefiningClass();
+
+ /**
+ * Gets the name of this member.
+ *
+ * @return The name of this field
+ */
+ @Nonnull String getName();
+
+ /**
+ * Gets the access flags for this member.
+ *
+ * This will be a combination of the AccessFlags.* flags that are marked as compatible for use with this type
+ * of member.
+ *
+ * @return The access flags for this member
+ */
+ int getAccessFlags();
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Method.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Method.java
index c9080e0f..5d3796ff 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/iface/Method.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Method.java
@@ -44,7 +44,7 @@ import java.util.Set;
* It also acts as a MethodReference to itself. Any equality/comparison is based on its identity as a MethodReference,
* and shouldn't take into account any non-MethodReference specifics of this method.
*/
-public interface Method extends MethodReference {
+public interface Method extends MethodReference, Member {
/**
* Gets the type of the class that defines this method.
*
@@ -86,7 +86,7 @@ public interface Method extends MethodReference {
*
* @return The access flags for this method
*/
- int getAccessFlags();
+ @Override int getAccessFlags();
/**
* Gets a set of the annotations that are applied to this method.
@@ -95,7 +95,7 @@ public interface Method extends MethodReference {
*
* @return A set of the annotations that are applied to this method
*/
- @Nonnull Set<? extends Annotation> getAnnotations();
+ @Override @Nonnull Set<? extends Annotation> getAnnotations();
/**
* Gets a MethodImplementation object that defines the implementation of the method.
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/OneFixedFourParameterRegisterInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/OneFixedFourParameterRegisterInstruction.java
new file mode 100644
index 00000000..c03bff77
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/OneFixedFourParameterRegisterInstruction.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2015, 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.instruction;
+
+public interface OneFixedFourParameterRegisterInstruction extends VariableRegisterInstruction {
+ int getRegisterFixedC();
+ int getRegisterParameterD();
+ int getRegisterParameterE();
+ int getRegisterParameterF();
+ int getRegisterParameterG();
+
+ /** Returns the count of just the parameter register counts; in range of [0, 4] */
+ int getParameterRegisterCount();
+
+ /** Includes the total sum of both fixed and parameter register counts; at least 1 */
+ @Override
+ int getRegisterCount();
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/formats/Instruction25x.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/formats/Instruction25x.java
new file mode 100644
index 00000000..51df2dee
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/formats/Instruction25x.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015, 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.instruction.formats;
+
+import org.jf.dexlib2.iface.instruction.OneFixedFourParameterRegisterInstruction;
+
+public interface Instruction25x extends OneFixedFourParameterRegisterInstruction {
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java
index 432f1930..ed50ef5b 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java
@@ -97,6 +97,8 @@ public abstract class ImmutableInstruction implements Instruction {
return ImmutableInstruction22x.of((Instruction22x)instruction);
case Format23x:
return ImmutableInstruction23x.of((Instruction23x)instruction);
+ case Format25x:
+ return ImmutableInstruction25x.of((Instruction25x) instruction);
case Format30t:
return ImmutableInstruction30t.of((Instruction30t)instruction);
case Format31c:
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction25x.java b/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction25x.java
new file mode 100644
index 00000000..2f31eaea
--- /dev/null
+++ b/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction25x.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015, 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.immutable.instruction;
+
+import org.jf.dexlib2.Format;
+import org.jf.dexlib2.Opcode;
+import org.jf.dexlib2.iface.instruction.formats.Instruction25x;
+import org.jf.dexlib2.util.Preconditions;
+
+import javax.annotation.Nonnull;
+
+public class ImmutableInstruction25x extends ImmutableInstruction implements Instruction25x {
+ public static final Format FORMAT = Format.Format25x;
+
+ protected final int parameterRegisterCount;
+ protected final int registerClosure;
+ protected final int registerD;
+ protected final int registerE;
+ protected final int registerF;
+ protected final int registerG;
+
+ public ImmutableInstruction25x(@Nonnull Opcode opcode,
+ int parameterRegisterCount,
+ int registerClosure,
+ int registerD,
+ int registerE,
+ int registerF,
+ int registerG) {
+ super(opcode);
+ this.parameterRegisterCount =
+ Preconditions.check25xParameterRegisterCount(parameterRegisterCount);
+ this.registerClosure = Preconditions.checkNibbleRegister(registerClosure);
+ this.registerD = (parameterRegisterCount>0) ?
+ Preconditions.checkNibbleRegister(registerD) : 0;
+ this.registerE = (parameterRegisterCount>1) ?
+ Preconditions.checkNibbleRegister(registerE) : 0;
+ this.registerF = (parameterRegisterCount>2) ?
+ Preconditions.checkNibbleRegister(registerF) : 0;
+ this.registerG = (parameterRegisterCount>3) ?
+ Preconditions.checkNibbleRegister(registerG) : 0;
+ }
+
+ public static ImmutableInstruction25x of(Instruction25x instruction) {
+ if (instruction instanceof ImmutableInstruction25x) {
+ return (ImmutableInstruction25x)instruction;
+ }
+ return new ImmutableInstruction25x(
+ instruction.getOpcode(),
+ instruction.getRegisterCount(),
+ instruction.getRegisterFixedC(),
+ instruction.getRegisterParameterD(),
+ instruction.getRegisterParameterE(),
+ instruction.getRegisterParameterF(),
+ instruction.getRegisterParameterG());
+ }
+
+
+ @Override public int getParameterRegisterCount() { return parameterRegisterCount; }
+ @Override public int getRegisterCount() { return parameterRegisterCount + 1; }
+
+ @Override public int getRegisterFixedC() { return registerClosure; }
+ @Override public int getRegisterParameterD() { return registerD; }
+ @Override public int getRegisterParameterE() { return registerE; }
+ @Override public int getRegisterParameterF() { return registerF; }
+ @Override public int getRegisterParameterG() { return registerG; }
+
+ @Override public Format getFormat() { return FORMAT; }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java b/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java
index ab86b652..51c083ca 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java
@@ -133,6 +133,15 @@ public class Preconditions {
return registerCount;
}
+ public static int check25xParameterRegisterCount(int registerCount) {
+ if (registerCount < 0 || registerCount > 4) {
+ throw new IllegalArgumentException(
+ String.format("Invalid parameter register count: %d. " +
+ "Must be between 0 and 4, inclusive.", registerCount));
+ }
+ return registerCount;
+ }
+
public static int checkRegisterRangeCount(int registerCount) {
if ((registerCount & 0xFFFFFF00) != 0) {
throw new IllegalArgumentException(
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 87a2ae6b..0650ab3c 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java
@@ -572,10 +572,12 @@ public abstract class DexWriter<
}
}
-
private void writeAnnotationSets(@Nonnull DexDataWriter writer) throws IOException {
writer.align();
annotationSetSectionOffset = writer.getPosition();
+ if (shouldCreateEmptyAnnotationSet()) {
+ writer.writeInt(0);
+ }
for (Map.Entry<? extends AnnotationSetKey, Integer> entry: annotationSetSection.getItems()) {
Collection<? extends AnnotationKey> annotations = Ordering.from(BaseAnnotation.BY_TYPE)
.immutableSortedCopy(annotationSetSection.getAnnotations(entry.getKey()));
@@ -613,6 +615,8 @@ public abstract class DexWriter<
for (AnnotationSetKey annotationSetKey: parameterAnnotations) {
if (annotationSetSection.getAnnotations(annotationSetKey).size() > 0) {
writer.writeInt(annotationSetSection.getItemOffset(annotationSetKey));
+ } else if (shouldCreateEmptyAnnotationSet()) {
+ writer.writeInt(annotationSetSectionOffset);
} else {
writer.writeInt(NO_OFFSET);
}
@@ -971,6 +975,9 @@ public abstract class DexWriter<
case Format23x:
instructionWriter.write((Instruction23x)instruction);
break;
+ case Format25x:
+ instructionWriter.write((Instruction25x)instruction);
+ break;
case Format30t:
instructionWriter.write((Instruction30t)instruction);
break;
@@ -1115,7 +1122,7 @@ public abstract class DexWriter<
if (annotationSection.getItems().size() > 0) {
numItems++;
}
- if (annotationSetSection.getItems().size() > 0) {
+ if (annotationSetSection.getItems().size() > 0 || shouldCreateEmptyAnnotationSet()) {
numItems++;
}
if (numAnnotationSetRefItems > 0) {
@@ -1163,8 +1170,8 @@ public abstract class DexWriter<
writeMapItem(writer, ItemType.TYPE_LIST, typeListSection.getItems().size(), typeListSectionOffset);
writeMapItem(writer, ItemType.ENCODED_ARRAY_ITEM, numEncodedArrayItems, encodedArraySectionOffset);
writeMapItem(writer, ItemType.ANNOTATION_ITEM, annotationSection.getItems().size(), annotationSectionOffset);
- writeMapItem(writer, ItemType.ANNOTATION_SET_ITEM, annotationSetSection.getItems().size(),
- annotationSetSectionOffset);
+ writeMapItem(writer, ItemType.ANNOTATION_SET_ITEM,
+ annotationSetSection.getItems().size() + (shouldCreateEmptyAnnotationSet() ? 1 : 0), annotationSetSectionOffset);
writeMapItem(writer, ItemType.ANNOTATION_SET_REF_LIST, numAnnotationSetRefItems, annotationSetRefSectionOffset);
writeMapItem(writer, ItemType.ANNOTATION_DIRECTORY_ITEM, numAnnotationDirectoryItems,
annotationDirectorySectionOffset);
@@ -1226,4 +1233,11 @@ public abstract class DexWriter<
writer.writeInt(0);
}
}
+
+ private boolean shouldCreateEmptyAnnotationSet() {
+ // Workaround for a crash in Dalvik VM before Jelly Bean MR1 (4.2)
+ // which is triggered by NO_OFFSET in parameter annotation list.
+ // (https://code.google.com/p/android/issues/detail?id=35304)
+ return (api < 17);
+ }
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedValueWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedValueWriter.java
index 53d77b39..def326c6 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedValueWriter.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedValueWriter.java
@@ -31,7 +31,9 @@
package org.jf.dexlib2.writer;
+import com.google.common.collect.Ordering;
import org.jf.dexlib2.ValueType;
+import org.jf.dexlib2.base.BaseAnnotationElement;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.dexlib2.iface.reference.MethodReference;
@@ -40,7 +42,8 @@ import java.io.IOException;
import java.util.Collection;
public abstract class EncodedValueWriter<StringKey, TypeKey, FieldRefKey extends FieldReference,
- MethodRefKey extends MethodReference, AnnotationElement, EncodedValue> {
+ MethodRefKey extends MethodReference, AnnotationElement extends org.jf.dexlib2.iface.AnnotationElement,
+ EncodedValue> {
@Nonnull private final DexDataWriter writer;
@Nonnull private final StringSection<StringKey, ?> stringSection;
@Nonnull private final TypeSection<?, TypeKey, ?> typeSection;
@@ -70,7 +73,11 @@ public abstract class EncodedValueWriter<StringKey, TypeKey, FieldRefKey extends
writer.writeEncodedValueHeader(ValueType.ANNOTATION, 0);
writer.writeUleb128(typeSection.getItemIndex(annotationType));
writer.writeUleb128(elements.size());
- for (AnnotationElement element: elements) {
+
+ Collection<? extends AnnotationElement> sortedElements = Ordering.from(BaseAnnotationElement.BY_NAME)
+ .immutableSortedCopy(elements);
+
+ for (AnnotationElement element: sortedElements) {
writer.writeUleb128(stringSection.getItemIndex(annotationSection.getElementName(element)));
writeEncodedValue(annotationSection.getElementValue(element));
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java
index 17d31337..c9aa73a1 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java
@@ -31,6 +31,8 @@
package org.jf.dexlib2.writer;
+import com.google.common.collect.Ordering;
+import com.google.common.primitives.Ints;
import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
import org.jf.dexlib2.iface.instruction.SwitchElement;
@@ -43,6 +45,7 @@ import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
import java.io.IOException;
+import java.util.Comparator;
import java.util.List;
public class InstructionWriter<StringRef extends StringReference, TypeRef extends TypeReference,
@@ -317,6 +320,19 @@ public class InstructionWriter<StringRef extends StringReference, TypeRef extend
}
}
+ public void write(@Nonnull Instruction25x instruction) {
+ try {
+ writer.write(instruction.getOpcode().value);
+ writer.write(packNibbles(
+ instruction.getRegisterParameterG(), instruction.getParameterRegisterCount()));
+ writer.write(packNibbles(
+ instruction.getRegisterFixedC(), instruction.getRegisterParameterD()));
+ writer.write(packNibbles(
+ instruction.getRegisterParameterE(), instruction.getRegisterParameterF()));
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
public void write(@Nonnull Instruction3rc instruction) {
try {
writer.write(instruction.getOpcode().value);
@@ -378,7 +394,8 @@ public class InstructionWriter<StringRef extends StringReference, TypeRef extend
try {
writer.writeUbyte(0);
writer.writeUbyte(instruction.getOpcode().value >> 8);
- List<? extends SwitchElement> elements = instruction.getSwitchElements();
+ List<? extends SwitchElement> elements = Ordering.from(switchElementComparator).immutableSortedCopy(
+ instruction.getSwitchElements());
writer.writeUshort(elements.size());
for (SwitchElement element: elements) {
writer.writeInt(element.getKey());
@@ -391,6 +408,12 @@ public class InstructionWriter<StringRef extends StringReference, TypeRef extend
}
}
+ private final Comparator<SwitchElement> switchElementComparator = new Comparator<SwitchElement>() {
+ @Override public int compare(SwitchElement element1, SwitchElement element2) {
+ return Ints.compare(element1.getKey(), element2.getKey());
+ }
+ };
+
public void write(@Nonnull PackedSwitchPayload instruction) {
try {
writer.writeUbyte(0);
diff --git a/dexlib2/src/accessorTest/java/org/jf/dexlib2/AccessorTest.java b/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java
index 13e7b30f..924d3fd3 100644
--- a/dexlib2/src/accessorTest/java/org/jf/dexlib2/AccessorTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java
@@ -79,7 +79,7 @@ public class AccessorTest {
public void testAccessors() throws IOException {
URL url = AccessorTest.class.getClassLoader().getResource("accessorTest.dex");
Assert.assertNotNull(url);
- DexFile f = DexFileFactory.loadDexFile(url.getFile(), 15);
+ DexFile f = DexFileFactory.loadDexFile(url.getFile(), 15, false);
SyntheticAccessorResolver sar = new SyntheticAccessorResolver(f.getClasses());
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 686b2101..65a82f05 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java
@@ -69,7 +69,7 @@ public class CustomMethodInlineTableTest {
DexFile dexFile = new ImmutableDexFile(ImmutableList.of(classDef));
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
- 15);
+ 15, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver);
@@ -96,7 +96,7 @@ public class CustomMethodInlineTableTest {
DexFile dexFile = new ImmutableDexFile(ImmutableList.of(classDef));
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
- 15);
+ 15, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver);
@@ -123,7 +123,7 @@ public class CustomMethodInlineTableTest {
DexFile dexFile = new ImmutableDexFile(ImmutableList.of(classDef));
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
- 15);
+ 15, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver);
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 b2cc52db..8ba975a1 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java
@@ -41,10 +41,12 @@ import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.Annotation;
import org.jf.dexlib2.iface.AnnotationElement;
import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.value.AnnotationEncodedValue;
import org.jf.dexlib2.immutable.ImmutableAnnotation;
import org.jf.dexlib2.immutable.ImmutableAnnotationElement;
import org.jf.dexlib2.immutable.ImmutableClassDef;
import org.jf.dexlib2.immutable.ImmutableDexFile;
+import org.jf.dexlib2.immutable.value.ImmutableAnnotationEncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableNullEncodedValue;
import org.jf.dexlib2.writer.io.MemoryDataStore;
import org.jf.dexlib2.writer.pool.DexPool;
@@ -75,7 +77,7 @@ public class DexWriterTest {
throw new RuntimeException(ex);
}
- DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15), dataStore.getData());
+ DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15, false), dataStore.getData());
ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(dbClassDef);
Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);
@@ -87,4 +89,48 @@ public class DexWriterTest {
Assert.assertEquals("blah", dbElements.get(0).getName());
Assert.assertEquals("zabaglione", dbElements.get(1).getName());
}
+
+ @Test
+ public void testEncodedAnnotationElementOrder() {
+ // Elements are out of order wrt to the element name
+ ImmutableSet<ImmutableAnnotationElement> encodedElements =
+ ImmutableSet.of(new ImmutableAnnotationElement("zabaglione", ImmutableNullEncodedValue.INSTANCE),
+ new ImmutableAnnotationElement("blah", ImmutableNullEncodedValue.INSTANCE));
+
+ ImmutableAnnotationEncodedValue encodedAnnotations =
+ new ImmutableAnnotationEncodedValue("Lan/encoded/annotation", encodedElements);
+
+ ImmutableSet<ImmutableAnnotationElement> elements =
+ ImmutableSet.of(new ImmutableAnnotationElement("encoded_annotation", encodedAnnotations));
+
+ ImmutableAnnotation annotation = new ImmutableAnnotation(AnnotationVisibility.RUNTIME,
+ "Lorg/test/anno;", elements);
+
+ ImmutableClassDef classDef = new ImmutableClassDef("Lorg/test/blah;",
+ 0, "Ljava/lang/Object;", null, null, ImmutableSet.of(annotation), null, null);
+
+ MemoryDataStore dataStore = new MemoryDataStore();
+
+ try {
+ DexPool.writeTo(dataStore, new ImmutableDexFile(ImmutableSet.of(classDef)));
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15, false), dataStore.getData());
+ ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null);
+ Assert.assertNotNull(dbClassDef);
+ Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null);
+ Assert.assertNotNull(dbAnnotation);
+
+ AnnotationElement element = Iterables.getFirst(dbAnnotation.getElements(), null);
+ AnnotationEncodedValue dbAnnotationEncodedValue = (AnnotationEncodedValue)element.getValue();
+
+ List<AnnotationElement> dbElements = Lists.newArrayList(dbAnnotationEncodedValue.getElements());
+
+ // Ensure that the elements were written out in sorted order
+ Assert.assertEquals(2, dbElements.size());
+ Assert.assertEquals("blah", dbElements.get(0).getName());
+ Assert.assertEquals("zabaglione", dbElements.get(1).getName());
+ }
}
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 38015e74..7e504a17 100644
--- a/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java
+++ b/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java
@@ -92,7 +92,7 @@ public class JumboStringConversionTest {
MemoryDataStore dexStore = new MemoryDataStore();
dexBuilder.writeTo(dexStore);
- DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15), dexStore.getData());
+ DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15, false), dexStore.getData());
ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(classDef);
@@ -189,7 +189,7 @@ public class JumboStringConversionTest {
MemoryDataStore dexStore = new MemoryDataStore();
dexBuilder.writeTo(dexStore);
- DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15), dexStore.getData());
+ DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15, false), dexStore.getData());
ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null);
Assert.assertNotNull(classDef);
diff --git a/examples/HelloWorldLambda/HelloWorldFunctionalInterface.smali b/examples/HelloWorldLambda/HelloWorldFunctionalInterface.smali
new file mode 100644
index 00000000..c2ada47d
--- /dev/null
+++ b/examples/HelloWorldLambda/HelloWorldFunctionalInterface.smali
@@ -0,0 +1,8 @@
+# Functional interface used by HelloWorld.smali
+# Required in order to reify the lambda with create-lambda or unbox-lambda instructions
+
+.class public abstract interface LHelloWorldFunctionalInterface;
+.super Ljava/lang/Object;
+
+.method public abstract applyFourStrings(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+.end method
diff --git a/examples/HelloWorldLambda/HelloWorldLambda.smali b/examples/HelloWorldLambda/HelloWorldLambda.smali
new file mode 100644
index 00000000..36c8e49c
--- /dev/null
+++ b/examples/HelloWorldLambda/HelloWorldLambda.smali
@@ -0,0 +1,57 @@
+.class public LHelloWorldLambda;
+
+#Ye olde hello world application (with lambdas!)
+#To assemble and run this on a phone or emulator:
+#
+#java -jar smali.jar -o classes.dex HelloWorldLambda.smali HelloWorldFunctionalInterface.smali
+#zip HelloWorld.zip classes.dex
+#adb push HelloWorld.zip /data/local
+#adb shell dalvikvm -cp /data/local/HelloWorld.zip HelloWorld
+#
+#if you get out of memory type errors when running smali.jar, try
+#java -Xmx512m -jar smali.jar HelloWorldLambda.smali
+#instead
+
+.super Ljava/lang/Object;
+
+.method public static main([Ljava/lang/String;)V
+ .registers 9 # 1 parameter, 8 locals
+
+ sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+
+ const-string v1, "Hello World!"
+ const-string v2, "How" # vD
+ const-string v3, "are" # vE
+ const-string v4, "you" # vF
+ const-string v5, "doing?" # vG
+
+ capture-variable v1, "helloworld"
+
+ # TODO: do I need to pass the type of the lambda's functional interface here as a type id?
+ create-lambda v1, LHelloWorldLambda;->doHelloWorld(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ # Method descriptor is not required here, because only the single-abstract method is ever invoked.
+ invoke-lambda v1, {v2, v3, v4, v5}
+
+ box-lambda v6, v1 # The type of v6 is now 'LHelloWorldFunctionalInterface;'
+ invoke-virtual {v6, v2, v3, v4, v5}, LHelloWorldFunctionalInterface;->applyFourStrings(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+
+ # FIXME: should be \HelloWorldFunctionalInterface; instead of L...;
+
+ # TODO: do we really need the type descriptor here at all?
+ unbox-lambda v7, v6, LHelloWorldFunctionalInterface; # The type of v7 is now \HelloWorldFunctionalInterface;
+ invoke-lambda v7, {v2, v3, v4, v5}
+
+ return-void
+.end method
+
+.method public static doHelloWorld(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ .registers 6 # 4 parameters, 2 locals
+
+ # This helloworld variable is brought to you by the variable liberation front
+ liberate-variable v0, p0, "helloworld"
+
+ sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
+ invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+ return-void
+.end method
diff --git a/gradle.properties b/gradle.properties
index 335740c6..567bbb91 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1 +1,2 @@
-org.gradle.daemon=true \ No newline at end of file
+org.gradle.daemon=true
+org.gradle.parallel=true \ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 58385981..085a1cdc 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 3f70b064..7e793277 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Feb 02 12:47:14 PST 2014
+#Sun Mar 01 19:20:47 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip
diff --git a/smali/build.gradle b/smali/build.gradle
index 0f43d756..0e5cbf2d 100644
--- a/smali/build.gradle
+++ b/smali/build.gradle
@@ -29,25 +29,28 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-configurations {
- antlr3
- jflex
- proguard
-}
-
-ext.antlrSource = 'src/main/antlr3'
-ext.antlrOutput = file("${buildDir}/generated-sources/antlr3")
+apply plugin: 'antlr'
+apply plugin: 'jflex'
-ext.jflexSource = "src/main/jflex"
-ext.jflexOutput = file("${buildDir}/generated-sources/jflex")
-
-ext.testAntlrSource = 'src/test/antlr3'
-ext.testAntlrOutput = file("${buildDir}/generated-test-sources/antlr3")
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath depends.jflex_plugin
+ classpath depends.proguard_gradle
+ }
+}
-sourceSets.main.java.srcDir antlrOutput
-sourceSets.main.java.srcDir jflexOutput
+configurations {
+ // Remove the full antlr library that's added by the antlr plugin. We manually
+ // add the smaller antlr_runtime library instead
+ compile.exclude group: 'org.antlr', module: 'antlr'
-sourceSets.test.java.srcDir testAntlrOutput
+ // The jflex lexer doesn't have any runtime dependencies, so remove the dependency
+ // that gets added by the jflex plugin
+ compile.exclude group: 'de.jflex', module: 'jflex'
+}
idea {
module {
@@ -73,68 +76,10 @@ dependencies {
testCompile depends.junit
- antlr3 depends.antlr
+ antlr depends.antlr
jflex depends.jflex
- proguard depends.proguard
}
-task generateParserAntlrSource(type: JavaExec) {
- inputs.dir file(antlrSource)
- outputs.dir file(antlrOutput)
-
- mkdir(antlrOutput)
- def grammars = fileTree(antlrSource).include('**/smaliParser.g')
-
- classpath = configurations.antlr3
- main = 'org.antlr.Tool'
- args '-fo', relativePath("${antlrOutput}/org/jf/smali")
- args grammars.files
-}
-
-task generateTreeWalkerAntlrSource(type: JavaExec) {
- inputs.dir file(antlrSource)
- outputs.dir file(antlrOutput)
-
- mkdir(antlrOutput)
- def grammars = fileTree(antlrSource).include('**/smaliTreeWalker.g')
-
- classpath = configurations.antlr3
- main = 'org.antlr.Tool'
- args '-fo', relativePath("${antlrOutput}/org/jf/smali")
- args grammars.files
-}
-
-task generateTestAntlrSource(type: JavaExec) {
- inputs.dir file(testAntlrSource)
- outputs.dir file(testAntlrOutput)
-
- mkdir(testAntlrOutput)
- def grammars = fileTree(testAntlrSource).include('**/*.g')
-
- classpath = configurations.antlr3
- main = 'org.antlr.Tool'
- args '-fo', relativePath("${testAntlrOutput}/org/jf/smali")
- args grammars.files.join(' ')
-}
-
-task generateJflexSource(type: JavaExec) {
- inputs.dir file(jflexSource)
- outputs.dir file(jflexOutput)
-
- mkdir(jflexOutput)
- def grammars = fileTree(jflexSource).include('**/*.flex')
-
- classpath = configurations.jflex
- main = 'JFlex.Main'
- args '-q'
- args '-d', relativePath("${jflexOutput}/org/jf/smali")
- args grammars.files.join(' ')
- environment LC_ALL: "en_US"
-}
-
-compileJava.dependsOn generateParserAntlrSource, generateTreeWalkerAntlrSource, generateJflexSource
-compileTestJava.dependsOn generateTestAntlrSource
-
processResources.inputs.property('version', version)
processResources.expand('version': version)
@@ -149,17 +94,29 @@ task fatJar(type: Jar, dependsOn: jar) {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
manifest {
- attributes("Main-Class": "org.jf.smali.main")
+ attributes('Main-Class': 'org.jf.smali.main')
}
doLast {
- if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
+ if (!System.getProperty('os.name').toLowerCase().contains('windows')) {
ant.symlink(link: file("${destinationDir}/smali.jar"), resource: archivePath, overwrite: true)
}
}
}
tasks.getByPath('build').dependsOn(fatJar)
+generateTestGrammarSource {
+ outputDirectory = new File(outputDirectory, 'org/jf/smali')
+}
+
+generateGrammarSource {
+ outputDirectory = new File(outputDirectory, 'org/jf/smali')
+}
+
+generateJFlexSource {
+ outputDirectory = new File(outputDirectory, 'org/jf/smali')
+}
+
uploadArchives {
repositories.mavenDeployer {
pom.project {
@@ -171,22 +128,23 @@ uploadArchives {
}
}
-task proguard(type: JavaExec, dependsOn: fatJar) {
- def outFile = fatJar.destinationDir.getPath() + '/' + fatJar.baseName + '-' + fatJar.version + '-small' + '.' + fatJar.extension
- inputs.file fatJar.archivePath
- outputs.file outFile
-
- classpath = configurations.proguard
- main = 'proguard.ProGuard'
- args "-injars ${fatJar.archivePath}(!**/TestStringTemplate*.class)"
- args "-outjars ${outFile}"
- args "-libraryjars ${System.properties['java.home']}/lib/rt.jar"
- args '-dontobfuscate'
- args '-dontoptimize'
- args '-keep public class org.jf.smali.main { public static void main(java.lang.String[]); }'
- args '-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }'
- args '-dontwarn com.google.common.**'
- args '-dontnote com.google.common.**'
+task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) {
+ def outFile = fatJar.destinationDir.getPath() + '/' + fatJar.baseName + '-' +
+ fatJar.version + '-small' + '.' + fatJar.extension
+
+ injars fatJar.archivePath
+ outjars outFile
+
+ libraryjars "${System.properties['java.home']}/lib/rt.jar"
+
+ dontobfuscate
+ dontoptimize
+
+ 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.**'
+ dontnote 'com.google.common.**'
}
tasks.getByPath(':release').dependsOn(proguard)
diff --git a/smali/src/main/antlr3/smaliParser.g b/smali/src/main/antlr/smaliParser.g
index d057b4a4..6d07452c 100644
--- a/smali/src/main/antlr3/smaliParser.g
+++ b/smali/src/main/antlr/smaliParser.g
@@ -86,6 +86,8 @@ tokens {
INSTRUCTION_FORMAT21c_FIELD_ODEX;
INSTRUCTION_FORMAT21c_STRING;
INSTRUCTION_FORMAT21c_TYPE;
+ INSTRUCTION_FORMAT21c_LAMBDA;
+ INSTRUCTION_FORMAT21c_METHOD;
INSTRUCTION_FORMAT21ih;
INSTRUCTION_FORMAT21lh;
INSTRUCTION_FORMAT21s;
@@ -94,12 +96,14 @@ tokens {
INSTRUCTION_FORMAT22c_FIELD;
INSTRUCTION_FORMAT22c_FIELD_ODEX;
INSTRUCTION_FORMAT22c_TYPE;
+ INSTRUCTION_FORMAT22c_STRING;
INSTRUCTION_FORMAT22cs_FIELD;
INSTRUCTION_FORMAT22s;
INSTRUCTION_FORMAT22s_OR_ID;
INSTRUCTION_FORMAT22t;
INSTRUCTION_FORMAT22x;
INSTRUCTION_FORMAT23x;
+ INSTRUCTION_FORMAT25x;
INSTRUCTION_FORMAT30t;
INSTRUCTION_FORMAT31c;
INSTRUCTION_FORMAT31i;
@@ -209,6 +213,8 @@ tokens {
I_STATEMENT_FORMAT21c_TYPE;
I_STATEMENT_FORMAT21c_FIELD;
I_STATEMENT_FORMAT21c_STRING;
+ I_STATEMENT_FORMAT21c_LAMBDA;
+ I_STATEMENT_FORMAT21c_METHOD;
I_STATEMENT_FORMAT21ih;
I_STATEMENT_FORMAT21lh;
I_STATEMENT_FORMAT21s;
@@ -216,10 +222,12 @@ tokens {
I_STATEMENT_FORMAT22b;
I_STATEMENT_FORMAT22c_FIELD;
I_STATEMENT_FORMAT22c_TYPE;
+ I_STATEMENT_FORMAT22c_STRING;
I_STATEMENT_FORMAT22s;
I_STATEMENT_FORMAT22t;
I_STATEMENT_FORMAT22x;
I_STATEMENT_FORMAT23x;
+ I_STATEMENT_FORMAT25x;
I_STATEMENT_FORMAT30t;
I_STATEMENT_FORMAT31c;
I_STATEMENT_FORMAT31i;
@@ -252,7 +260,7 @@ import org.jf.dexlib2.Opcodes;
private boolean verboseErrors = false;
private boolean allowOdex = false;
private int apiLevel = 15;
- private Opcodes opcodes = new Opcodes(apiLevel);
+ private Opcodes opcodes = new Opcodes(apiLevel, false);
public void setVerboseErrors(boolean verboseErrors) {
this.verboseErrors = verboseErrors;
@@ -262,8 +270,8 @@ import org.jf.dexlib2.Opcodes;
this.allowOdex = allowOdex;
}
- public void setApiLevel(int apiLevel) {
- this.opcodes = new Opcodes(apiLevel);
+ public void setApiLevel(int apiLevel, boolean experimental) {
+ this.opcodes = new Opcodes(apiLevel, experimental);
this.apiLevel = apiLevel;
}
@@ -561,14 +569,18 @@ simple_name
| INSTRUCTION_FORMAT21c_FIELD_ODEX -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_FIELD_ODEX]
| INSTRUCTION_FORMAT21c_STRING -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_STRING]
| INSTRUCTION_FORMAT21c_TYPE -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_TYPE]
+ | INSTRUCTION_FORMAT21c_LAMBDA -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_LAMBDA]
+ | INSTRUCTION_FORMAT21c_METHOD -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_METHOD]
| INSTRUCTION_FORMAT21t -> SIMPLE_NAME[$INSTRUCTION_FORMAT21t]
| INSTRUCTION_FORMAT22c_FIELD -> SIMPLE_NAME[$INSTRUCTION_FORMAT22c_FIELD]
| INSTRUCTION_FORMAT22c_FIELD_ODEX -> SIMPLE_NAME[$INSTRUCTION_FORMAT22c_FIELD_ODEX]
| INSTRUCTION_FORMAT22c_TYPE -> SIMPLE_NAME[$INSTRUCTION_FORMAT22c_TYPE]
+ | INSTRUCTION_FORMAT22c_STRING -> SIMPLE_NAME[$INSTRUCTION_FORMAT22c_STRING]
| INSTRUCTION_FORMAT22cs_FIELD -> SIMPLE_NAME[$INSTRUCTION_FORMAT22cs_FIELD]
| INSTRUCTION_FORMAT22s_OR_ID -> SIMPLE_NAME[$INSTRUCTION_FORMAT22s_OR_ID]
| INSTRUCTION_FORMAT22t -> SIMPLE_NAME[$INSTRUCTION_FORMAT22t]
| INSTRUCTION_FORMAT23x -> SIMPLE_NAME[$INSTRUCTION_FORMAT23x]
+ | INSTRUCTION_FORMAT25x -> SIMPLE_NAME[$INSTRUCTION_FORMAT25x]
| INSTRUCTION_FORMAT31i_OR_ID -> SIMPLE_NAME[$INSTRUCTION_FORMAT31i_OR_ID]
| INSTRUCTION_FORMAT31t -> SIMPLE_NAME[$INSTRUCTION_FORMAT31t]
| INSTRUCTION_FORMAT35c_METHOD -> SIMPLE_NAME[$INSTRUCTION_FORMAT35c_METHOD]
@@ -806,6 +818,8 @@ instruction
| insn_format21c_field_odex
| insn_format21c_string
| insn_format21c_type
+ | insn_format21c_lambda
+ | insn_format21c_method
| insn_format21ih
| insn_format21lh
| insn_format21s
@@ -814,11 +828,13 @@ instruction
| insn_format22c_field
| insn_format22c_field_odex
| insn_format22c_type
+ | insn_format22c_string
| insn_format22cs_field
| insn_format22s
| insn_format22t
| insn_format22x
| insn_format23x
+ | insn_format25x
| insn_format30t
| insn_format31c
| insn_format31i
@@ -912,6 +928,16 @@ insn_format21c_type
INSTRUCTION_FORMAT21c_TYPE REGISTER COMMA nonvoid_type_descriptor
-> ^(I_STATEMENT_FORMAT21c_TYPE[$start, "I_STATEMENT_FORMAT21c"] INSTRUCTION_FORMAT21c_TYPE REGISTER nonvoid_type_descriptor);
+insn_format21c_lambda
+ : //e.g. capture-variable v1, "foobar"
+ INSTRUCTION_FORMAT21c_LAMBDA REGISTER COMMA STRING_LITERAL
+ -> ^(I_STATEMENT_FORMAT21c_LAMBDA[$start, "I_STATEMENT_FORMAT21c_LAMBDA"] INSTRUCTION_FORMAT21c_LAMBDA REGISTER STRING_LITERAL);
+
+insn_format21c_method
+ : //e.g. create-lambda v1, java/io/PrintStream/print(Ljava/lang/Stream;)V
+ INSTRUCTION_FORMAT21c_METHOD REGISTER COMMA method_reference
+ -> ^(I_STATEMENT_FORMAT21c_METHOD[$start, "I_STATEMENT_FORMAT21c_METHOD"] INSTRUCTION_FORMAT21c_METHOD REGISTER method_reference);
+
insn_format21ih
: //e.g. const/high16 v1, 1234
INSTRUCTION_FORMAT21ih REGISTER COMMA fixed_32bit_literal
@@ -957,6 +983,11 @@ insn_format22c_type
INSTRUCTION_FORMAT22c_TYPE REGISTER COMMA REGISTER COMMA nonvoid_type_descriptor
-> ^(I_STATEMENT_FORMAT22c_TYPE[$start, "I_STATEMENT_FORMAT22c_TYPE"] INSTRUCTION_FORMAT22c_TYPE REGISTER REGISTER nonvoid_type_descriptor);
+insn_format22c_string
+ : //e.g. liberate-variable v0, v1, "baz"
+ INSTRUCTION_FORMAT22c_STRING REGISTER COMMA REGISTER COMMA STRING_LITERAL
+ -> ^(I_STATEMENT_FORMAT22c_STRING[$start, "I_STATEMENT_FORMAT22c_STRING"] INSTRUCTION_FORMAT22c_STRING REGISTER REGISTER STRING_LITERAL);
+
insn_format22cs_field
: //e.g. iget-quick v0, v1, field@0xc
INSTRUCTION_FORMAT22cs_FIELD REGISTER COMMA REGISTER COMMA FIELD_OFFSET
@@ -984,6 +1015,11 @@ insn_format23x
INSTRUCTION_FORMAT23x REGISTER COMMA REGISTER COMMA REGISTER
-> ^(I_STATEMENT_FORMAT23x[$start, "I_STATEMENT_FORMAT23x"] INSTRUCTION_FORMAT23x REGISTER REGISTER REGISTER);
+insn_format25x
+ : //e.g. invoke-lambda vClosure, {vA, vB, vC, vD} -- up to 4 parameters + the closure.
+ INSTRUCTION_FORMAT25x REGISTER COMMA OPEN_BRACE register_list CLOSE_BRACE
+ -> ^(I_STATEMENT_FORMAT25x[$start, "I_STATEMENT_FORMAT25x"] INSTRUCTION_FORMAT25x REGISTER register_list);
+
insn_format30t
: //e.g. goto/32 endloop:
INSTRUCTION_FORMAT30t label_ref
diff --git a/smali/src/main/antlr3/smaliTreeWalker.g b/smali/src/main/antlr/smaliTreeWalker.g
index f49f10ef..8eed2b20 100644
--- a/smali/src/main/antlr3/smaliTreeWalker.g
+++ b/smali/src/main/antlr/smaliTreeWalker.g
@@ -77,15 +77,15 @@ import java.util.*;
public String classType;
private boolean verboseErrors = false;
private int apiLevel = 15;
- private Opcodes opcodes = new Opcodes(apiLevel);
+ private Opcodes opcodes = new Opcodes(apiLevel, false);
private DexBuilder dexBuilder;
public void setDexBuilder(DexBuilder dexBuilder) {
this.dexBuilder = dexBuilder;
}
- public void setApiLevel(int apiLevel) {
- this.opcodes = new Opcodes(apiLevel);
+ public void setApiLevel(int apiLevel, boolean experimental) {
+ this.opcodes = new Opcodes(apiLevel, experimental);
this.apiLevel = apiLevel;
}
@@ -532,7 +532,7 @@ registers_directive returns[boolean isLocalsDirective, int registers]
^(( I_REGISTERS {$isLocalsDirective = false;}
| I_LOCALS {$isLocalsDirective = true;}
)
- short_integral_literal {$registers = $short_integral_literal.value;}
+ short_integral_literal {$registers = $short_integral_literal.value & 0xFFFF;}
);
label_def
@@ -674,6 +674,22 @@ register_list returns[byte[\] registers, byte registerCount]
$registers[$registerCount++] = parseRegister_nibble($REGISTER.text);
})*);
+register_list4 returns[byte[\] registers, byte registerCount]
+ @init
+ {
+ $registers = new byte[4];
+ $registerCount = 0;
+ }
+ : ^(I_REGISTER_LIST
+ (REGISTER
+ {
+ if ($registerCount == 4) {
+ throw new SemanticException(input, $I_REGISTER_LIST, "A list4 of registers can only have a maximum of 4 " +
+ "registers. Use the <op>/range alternate opcode instead.");
+ }
+ $registers[$registerCount++] = parseRegister_nibble($REGISTER.text);
+ })*);
+
register_range returns[int startRegister, int endRegister]
: ^(I_REGISTER_RANGE (startReg=REGISTER endReg=REGISTER?)?)
{
@@ -726,6 +742,8 @@ instruction
| insn_format21c_field
| insn_format21c_string
| insn_format21c_type
+ | insn_format21c_lambda
+ | insn_format21c_method
| insn_format21ih
| insn_format21lh
| insn_format21s
@@ -733,10 +751,12 @@ instruction
| insn_format22b
| insn_format22c_field
| insn_format22c_type
+ | insn_format22c_string
| insn_format22s
| insn_format22t
| insn_format22x
| insn_format23x
+ | insn_format25x
| insn_format30t
| insn_format31c
| insn_format31i
@@ -861,6 +881,30 @@ insn_format21c_type
dexBuilder.internTypeReference($nonvoid_type_descriptor.type)));
};
+insn_format21c_lambda
+ : //e.g. capture-variable v1, "foobar"
+ ^(I_STATEMENT_FORMAT21c_LAMBDA INSTRUCTION_FORMAT21c_LAMBDA REGISTER string_literal)
+ {
+ Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT21c_LAMBDA.text);
+ short regA = parseRegister_byte($REGISTER.text);
+
+ $method::methodBuilder.addInstruction(new BuilderInstruction21c(opcode, regA,
+ dexBuilder.internStringReference($string_literal.value)));
+ };
+
+insn_format21c_method
+ : //e.g. create-lambda v1, java/io/PrintStream/print(Ljava/lang/Stream;)V
+ ^(I_STATEMENT_FORMAT21c_METHOD INSTRUCTION_FORMAT21c_METHOD REGISTER method_reference)
+ {
+ Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT21c_METHOD.text);
+ short regA = parseRegister_byte($REGISTER.text);
+
+ ImmutableMethodReference methodReference = $method_reference.methodReference;
+
+ $method::methodBuilder.addInstruction(new BuilderInstruction21c(opcode, regA,
+ dexBuilder.internMethodReference(methodReference)));
+ };
+
insn_format21ih
: //e.g. const/high16 v1, 1234
^(I_STATEMENT_FORMAT21ih INSTRUCTION_FORMAT21ih REGISTER fixed_32bit_literal)
@@ -947,6 +991,18 @@ insn_format22c_type
dexBuilder.internTypeReference($nonvoid_type_descriptor.type)));
};
+insn_format22c_string
+ : //e.g. liberate-variable v0, v1, "baz"
+ ^(I_STATEMENT_FORMAT22c_STRING INSTRUCTION_FORMAT22c_STRING registerA=REGISTER registerB=REGISTER string_literal)
+ {
+ Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT22c_STRING.text);
+ byte regA = parseRegister_nibble($registerA.text);
+ byte regB = parseRegister_nibble($registerB.text);
+
+ $method::methodBuilder.addInstruction(new BuilderInstruction22c(opcode, regA, regB,
+ dexBuilder.internStringReference($string_literal.value)));
+ };
+
insn_format22s
: //e.g. add-int/lit16 v0, v1, 12345
^(I_STATEMENT_FORMAT22s INSTRUCTION_FORMAT22s registerA=REGISTER registerB=REGISTER short_integral_literal)
@@ -994,6 +1050,23 @@ insn_format23x
$method::methodBuilder.addInstruction(new BuilderInstruction23x(opcode, regA, regB, regC));
};
+insn_format25x
+ : //e.g. invoke-lambda vClosure, {vD, vE, vF, vG} -- up to 4 parameters + the closure.
+ ^(I_STATEMENT_FORMAT25x INSTRUCTION_FORMAT25x REGISTER register_list4)
+ {
+ Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT25x.text);
+
+ byte closureRegister = parseRegister_nibble($REGISTER.text);
+
+ //this depends on the fact that register_list4 returns a byte[4]
+ byte[] registers = $register_list4.registers;
+ int parameterRegisterCount = $register_list4.registerCount; // don't count closure register
+
+ $method::methodBuilder.addInstruction(new BuilderInstruction25x(opcode,
+ parameterRegisterCount, closureRegister, registers[0], registers[1],
+ registers[2], registers[3]));
+ };
+
insn_format30t
: //e.g. goto/32 endloop:
^(I_STATEMENT_FORMAT30t INSTRUCTION_FORMAT30t label_ref)
diff --git a/smali/src/main/java/org/jf/smali/SmaliTestUtils.java b/smali/src/main/java/org/jf/smali/SmaliTestUtils.java
index 9fd73473..26de0089 100644
--- a/smali/src/main/java/org/jf/smali/SmaliTestUtils.java
+++ b/smali/src/main/java/org/jf/smali/SmaliTestUtils.java
@@ -48,10 +48,16 @@ import java.io.Reader;
import java.io.StringReader;
public class SmaliTestUtils {
+
public static ClassDef compileSmali(String smaliText) throws RecognitionException, IOException {
+ return compileSmali(smaliText, 15, false);
+ }
+
+ public static ClassDef compileSmali(String smaliText, int apiLevel, boolean experimental)
+ throws RecognitionException, IOException {
CommonTokenStream tokens;
LexerErrorInterface lexer;
- DexBuilder dexBuilder = DexBuilder.makeDexBuilder(15);
+ DexBuilder dexBuilder = DexBuilder.makeDexBuilder(apiLevel);
Reader reader = new StringReader(smaliText);
@@ -61,7 +67,7 @@ public class SmaliTestUtils {
smaliParser parser = new smaliParser(tokens);
parser.setVerboseErrors(true);
parser.setAllowOdex(false);
- parser.setApiLevel(15);
+ parser.setApiLevel(apiLevel, experimental);
smaliParser.smali_file_return result = parser.smali_file();
@@ -75,6 +81,7 @@ public class SmaliTestUtils {
treeStream.setTokenStream(tokens);
smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
+ dexGen.setApiLevel(apiLevel, experimental);
dexGen.setVerboseErrors(true);
dexGen.setDexBuilder(dexBuilder);
dexGen.smali_file();
@@ -87,7 +94,8 @@ public class SmaliTestUtils {
dexBuilder.writeTo(dataStore);
- DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15), dataStore.getData());
+ DexBackedDexFile dexFile = new DexBackedDexFile(
+ new Opcodes(apiLevel, experimental), 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
index c6095bf3..31f65a88 100644
--- a/smali/src/main/java/org/jf/smali/main.java
+++ b/smali/src/main/java/org/jf/smali/main.java
@@ -34,6 +34,7 @@ import org.antlr.runtime.Token;
import org.antlr.runtime.TokenSource;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;
+import org.antlr.runtime.tree.TreeNodeStream;
import org.apache.commons.cli.*;
import org.jf.dexlib2.writer.builder.DexBuilder;
import org.jf.dexlib2.writer.io.FileDataStore;
@@ -110,6 +111,7 @@ public class main {
boolean allowOdex = false;
boolean verboseErrors = false;
boolean printTokens = false;
+ boolean experimental = false;
int apiLevel = 15;
@@ -142,6 +144,9 @@ public class main {
case 'x':
allowOdex = true;
break;
+ case 'X':
+ experimental = true;
+ break;
case 'a':
apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
@@ -198,11 +203,12 @@ public class main {
final boolean finalPrintTokens = printTokens;
final boolean finalAllowOdex = allowOdex;
final int finalApiLevel = apiLevel;
+ final boolean finalExperimental = experimental;
for (final File file: filesToProcess) {
tasks.add(executor.submit(new Callable<Boolean>() {
@Override public Boolean call() throws Exception {
return assembleSmaliFile(file, dexBuilder, finalVerboseErrors, finalPrintTokens,
- finalAllowOdex, finalApiLevel);
+ finalAllowOdex, finalApiLevel, finalExperimental);
}
}));
}
@@ -252,7 +258,8 @@ public class main {
}
private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors,
- boolean printTokens, boolean allowOdex, int apiLevel)
+ boolean printTokens, boolean allowOdex, int apiLevel,
+ boolean experimental)
throws Exception {
CommonTokenStream tokens;
@@ -267,7 +274,7 @@ public class main {
if (printTokens) {
tokens.getTokens();
-
+
for (int i=0; i<tokens.size(); i++) {
Token token = tokens.get(i);
if (token.getChannel() == smaliParser.HIDDEN) {
@@ -276,12 +283,14 @@ public class main {
System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
}
+
+ System.out.flush();
}
smaliParser parser = new smaliParser(tokens);
parser.setVerboseErrors(verboseErrors);
parser.setAllowOdex(allowOdex);
- parser.setApiLevel(apiLevel);
+ parser.setApiLevel(apiLevel, experimental);
smaliParser.smali_file_return result = parser.smali_file();
@@ -294,7 +303,13 @@ public class main {
CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
treeStream.setTokenStream(tokens);
+ if (printTokens) {
+ System.out.println(t.toStringTree());
+ }
+
smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
+ dexGen.setApiLevel(apiLevel, experimental);
+
dexGen.setVerboseErrors(verboseErrors);
dexGen.setDexBuilder(dexBuilder);
dexGen.smali_file();
@@ -363,6 +378,11 @@ public class main {
.withArgName("API_LEVEL")
.create("a");
+ 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")
@@ -383,6 +403,7 @@ public class main {
basicOptions.addOption(outputOption);
basicOptions.addOption(allowOdexOption);
basicOptions.addOption(apiLevelOption);
+ basicOptions.addOption(experimentalOption);
basicOptions.addOption(jobsOption);
debugOptions.addOption(verboseErrorsOption);
diff --git a/smali/src/main/jflex/smaliLexer.flex b/smali/src/main/jflex/smaliLexer.jflex
index df571e66..bc17362f 100644
--- a/smali/src/main/jflex/smaliLexer.flex
+++ b/smali/src/main/jflex/smaliLexer.jflex
@@ -461,6 +461,14 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor}
return newToken(INSTRUCTION_FORMAT21c_TYPE);
}
+ "capture-variable" { // e.g. 'capture-variable vB, <string id>'
+ return newToken(INSTRUCTION_FORMAT21c_LAMBDA);
+ }
+
+ "create-lambda" { // e.g. 'create-lambda vClosure, <method id>'
+ return newToken(INSTRUCTION_FORMAT21c_METHOD);
+ }
+
"const/high16" {
return newToken(INSTRUCTION_FORMAT21ih);
}
@@ -492,10 +500,13 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor}
return newToken(INSTRUCTION_FORMAT22c_FIELD_ODEX);
}
- "instance-of" | "new-array" {
+ "instance-of" | "new-array" | "unbox-lambda" {
return newToken(INSTRUCTION_FORMAT22c_TYPE);
}
+ "liberate-variable" {
+ return newToken(INSTRUCTION_FORMAT22c_STRING);
+ }
"iget-quick" | "iget-wide-quick" | "iget-object-quick" | "iput-quick" | "iput-wide-quick" | "iput-object-quick" {
return newToken(INSTRUCTION_FORMAT22cs_FIELD);
}
@@ -513,7 +524,7 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor}
return newToken(INSTRUCTION_FORMAT22t);
}
- "move/from16" | "move-wide/from16" | "move-object/from16" {
+ "move/from16" | "move-wide/from16" | "move-object/from16" | "box-lambda" {
return newToken(INSTRUCTION_FORMAT22x);
}
@@ -527,6 +538,10 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor}
return newToken(INSTRUCTION_FORMAT23x);
}
+ "invoke-lambda" { // e.g. invoke-lambda vClosure, {vD, vE, vF, vG} -- at most 4 params
+ return newToken(INSTRUCTION_FORMAT25x);
+ }
+
"goto/32" {
return newToken(INSTRUCTION_FORMAT30t);
}
@@ -640,5 +655,5 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor}
"." { return invalidToken("Invalid directive"); }
"." [a-zA-z\-_] { return invalidToken("Invalid directive"); }
"." [a-zA-z\-_] [a-zA-z0-9\-_]* { return invalidToken("Invalid directive"); }
- . { return invalidToken("Invalid text"); }
+ [^] { return invalidToken("Invalid text"); }
}
diff --git a/smali/src/test/antlr3/org/jf/smali/expectedTokensTestGrammar.g b/smali/src/test/antlr/org/jf/smali/expectedTokensTestGrammar.g
index 6780b970..6780b970 100644
--- a/smali/src/test/antlr3/org/jf/smali/expectedTokensTestGrammar.g
+++ b/smali/src/test/antlr/org/jf/smali/expectedTokensTestGrammar.g
diff --git a/smali/src/test/java/LexerTest.java b/smali/src/test/java/LexerTest.java
index c1b70435..d156015a 100644
--- a/smali/src/test/java/LexerTest.java
+++ b/smali/src/test/java/LexerTest.java
@@ -40,6 +40,7 @@ import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
@@ -158,7 +159,7 @@ public class LexerTest {
if (smaliStream == null) {
Assert.fail("Could not load " + smaliFile);
}
- smaliFlexLexer lexer = new smaliFlexLexer(smaliStream);
+ smaliFlexLexer lexer = new smaliFlexLexer(new InputStreamReader(smaliStream));
lexer.setSourceFile(new File(test + ".smali"));
lexer.setSuppressErrors(true);
diff --git a/smali/src/test/resources/LexerTest/InstructionTest.smali b/smali/src/test/resources/LexerTest/InstructionTest.smali
index 2247adc0..f6dcbb1e 100644
--- a/smali/src/test/resources/LexerTest/InstructionTest.smali
+++ b/smali/src/test/resources/LexerTest/InstructionTest.smali
@@ -84,6 +84,8 @@ const-string
check-cast
new-instance
const-class
+capture-variable
+create-lambda
const/high16
const-wide/high16
const/16
@@ -122,6 +124,8 @@ iput-wide-volatile
iput-object-volatile
instance-of
new-array
+unbox-lambda
+liberate-variable
iget-quick
iget-wide-quick
iget-object-quick
@@ -144,6 +148,7 @@ if-le
move/from16
move-wide/from16
move-object/from16
+box-lambda
cmpl-float
cmpg-float
cmpl-double
@@ -194,6 +199,7 @@ add-double
sub-double
mul-double
div-double
+invoke-lambda
goto/32
const-string/jumbo
const
diff --git a/smali/src/test/resources/LexerTest/InstructionTest.tokens b/smali/src/test/resources/LexerTest/InstructionTest.tokens
index a5574ab4..fb5503b5 100644
--- a/smali/src/test/resources/LexerTest/InstructionTest.tokens
+++ b/smali/src/test/resources/LexerTest/InstructionTest.tokens
@@ -84,6 +84,8 @@ INSTRUCTION_FORMAT21c_STRING("const-string")
INSTRUCTION_FORMAT21c_TYPE("check-cast")
INSTRUCTION_FORMAT21c_TYPE("new-instance")
INSTRUCTION_FORMAT21c_TYPE("const-class")
+INSTRUCTION_FORMAT21c_LAMBDA("capture-variable")
+INSTRUCTION_FORMAT21c_METHOD("create-lambda")
INSTRUCTION_FORMAT21ih("const/high16")
INSTRUCTION_FORMAT21lh("const-wide/high16")
INSTRUCTION_FORMAT21s("const/16")
@@ -122,6 +124,8 @@ INSTRUCTION_FORMAT22c_FIELD_ODEX("iput-wide-volatile")
INSTRUCTION_FORMAT22c_FIELD_ODEX("iput-object-volatile")
INSTRUCTION_FORMAT22c_TYPE("instance-of")
INSTRUCTION_FORMAT22c_TYPE("new-array")
+INSTRUCTION_FORMAT22c_TYPE("unbox-lambda")
+INSTRUCTION_FORMAT22c_STRING("liberate-variable")
INSTRUCTION_FORMAT22cs_FIELD("iget-quick")
INSTRUCTION_FORMAT22cs_FIELD("iget-wide-quick")
INSTRUCTION_FORMAT22cs_FIELD("iget-object-quick")
@@ -144,6 +148,7 @@ INSTRUCTION_FORMAT22t("if-le")
INSTRUCTION_FORMAT22x("move/from16")
INSTRUCTION_FORMAT22x("move-wide/from16")
INSTRUCTION_FORMAT22x("move-object/from16")
+INSTRUCTION_FORMAT22x("box-lambda")
INSTRUCTION_FORMAT23x("cmpl-float")
INSTRUCTION_FORMAT23x("cmpg-float")
INSTRUCTION_FORMAT23x("cmpl-double")
@@ -194,6 +199,7 @@ INSTRUCTION_FORMAT23x("add-double")
INSTRUCTION_FORMAT23x("sub-double")
INSTRUCTION_FORMAT23x("mul-double")
INSTRUCTION_FORMAT23x("div-double")
+INSTRUCTION_FORMAT25x("invoke-lambda")
INSTRUCTION_FORMAT30t("goto/32")
INSTRUCTION_FORMAT31c("const-string/jumbo")
INSTRUCTION_FORMAT31i_OR_ID("const")
diff --git a/util/src/main/java/org/jf/util/ClassFileNameHandler.java b/util/src/main/java/org/jf/util/ClassFileNameHandler.java
index 67037817..a223d30e 100644
--- a/util/src/main/java/org/jf/util/ClassFileNameHandler.java
+++ b/util/src/main/java/org/jf/util/ClassFileNameHandler.java
@@ -68,7 +68,7 @@ public class ClassFileNameHandler {
public ClassFileNameHandler(File path, String fileExtension) {
this.top = new DirectoryEntry(path);
this.fileExtension = fileExtension;
- this.modifyWindowsReservedFilenames = testForWindowsReservedFileNames(path);
+ this.modifyWindowsReservedFilenames = isWindows();
}
// for testing
@@ -229,27 +229,8 @@ public class ClassFileNameHandler {
return sb.toString();
}
- private static boolean testForWindowsReservedFileNames(File path) {
- String[] reservedNames = new String[]{"aux", "con", "com1", "com9", "lpt1", "com9"};
-
- for (String reservedName: reservedNames) {
- File f = new File(path, reservedName + ".smali");
- if (f.exists()) {
- continue;
- }
-
- try {
- FileWriter writer = new FileWriter(f);
- writer.write("test");
- writer.flush();
- writer.close();
- f.delete(); //doesn't throw IOException
- } catch (IOException ex) {
- //if an exception occurred, it's likely that we're on a windows system.
- return true;
- }
- }
- return false;
+ private static boolean isWindows() {
+ return System.getProperty("os.name").startsWith("Windows");
}
private static Pattern reservedFileNameRegex = Pattern.compile("^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\\..*)?$",
diff --git a/util/src/main/java/org/jf/util/IndentingWriter.java b/util/src/main/java/org/jf/util/IndentingWriter.java
index 95d6c320..8e4ca628 100644
--- a/util/src/main/java/org/jf/util/IndentingWriter.java
+++ b/util/src/main/java/org/jf/util/IndentingWriter.java
@@ -120,7 +120,7 @@ public class IndentingWriter extends Writer {
int pos = start;
while (pos < end) {
pos = str.indexOf('\n', start);
- if (pos == -1) {
+ if (pos == -1 || pos >= end) {
writeLine(str, start, end-start);
return;
} else {
diff --git a/util/src/main/java/org/jf/util/TextUtils.java b/util/src/main/java/org/jf/util/TextUtils.java
index a01e68e7..66a1082d 100644
--- a/util/src/main/java/org/jf/util/TextUtils.java
+++ b/util/src/main/java/org/jf/util/TextUtils.java
@@ -50,10 +50,33 @@ public class TextUtils {
@Nonnull
public static String normalizeWhitespace(@Nonnull String source) {
+ // Go to native system new lines so that ^/$ work correctly
+ source = normalizeNewlines(source);
+
+ // Remove all suffix/prefix whitespace
+ Pattern pattern = Pattern.compile("((^[ \t]+)|([ \t]+$))", Pattern.MULTILINE);
+ Matcher matcher = pattern.matcher(source);
+ source = matcher.replaceAll("");
+
+ // Remove all empty lines
+ Pattern pattern2 = Pattern.compile("^\r?\n?", Pattern.MULTILINE);
+ Matcher matcher2 = pattern2.matcher(source);
+ source = matcher2.replaceAll("");
+
+ // Remove a trailing new line, if present
+ Pattern pattern3 = Pattern.compile("\r?\n?$");
+ Matcher matcher3 = pattern3.matcher(source);
+ source = matcher3.replaceAll("");
+
+ // Go back to unix-style \n newlines
source = normalizeNewlines(source, "\n");
+ return source;
+ }
- Pattern pattern = Pattern.compile("(\n[ \t]*)+");
+ @Nonnull
+ public static String stripComments(@Nonnull String source) {
+ Pattern pattern = Pattern.compile("#(.*)");
Matcher matcher = pattern.matcher(source);
- return matcher.replaceAll("\n");
+ return matcher.replaceAll("");
}
}
diff --git a/util/src/test/java/org/jf/util/TextUtilsTest.java b/util/src/test/java/org/jf/util/TextUtilsTest.java
new file mode 100644
index 00000000..ef14e03a
--- /dev/null
+++ b/util/src/test/java/org/jf/util/TextUtilsTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015, 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 org.junit.Assert;
+import org.junit.Test;
+
+public class TextUtilsTest {
+ @Test
+ public void testStripComments() {
+ Assert.assertEquals("", TextUtils.stripComments("#world"));
+ Assert.assertEquals("hello", TextUtils.stripComments("hello#world"));
+ Assert.assertEquals("multi\nline", TextUtils.stripComments("multi#hello world\nline#world"));
+ }
+
+ @Test
+ public void testNormalizeWhitespace() {
+ Assert.assertEquals("", TextUtils.normalizeWhitespace(" "));
+ Assert.assertEquals("hello", TextUtils.normalizeWhitespace("hello "));
+ Assert.assertEquals("hello", TextUtils.normalizeWhitespace(" hello"));
+ Assert.assertEquals("hello", TextUtils.normalizeWhitespace(" hello "));
+ Assert.assertEquals("hello\nworld", TextUtils.normalizeWhitespace("hello \n \n world"));
+ }
+} \ No newline at end of file